diff options
601 files changed, 18771 insertions, 7988 deletions
diff --git a/Android.bp b/Android.bp index 5292b2d80594..ffb4d3a6ff73 100644 --- a/Android.bp +++ b/Android.bp @@ -273,7 +273,8 @@ filegroup { // etc. ":framework-javastream-protos", - ":framework-statslog-gen", + ":framework-statslog-gen", // StatsLogInternal.java + ":statslog-framework-java-gen", // FrameworkStatsLog.java // telephony annotations ":framework-telephony-annotations", @@ -541,7 +542,7 @@ java_library { srcs: [":framework-all-sources"], installable: false, static_libs: [ - "exoplayer2-core", + "exoplayer2-extractor", "android.hardware.wifi-V1.0-java-constants", ], libs: ["icing-java-proto-lite"], @@ -561,7 +562,7 @@ java_library { "compat-changeid-annotation-processor", ], static_libs: [ - "exoplayer2-core", + "exoplayer2-extractor", "android.hardware.wifi-V1.0-java-constants", ] } @@ -582,7 +583,6 @@ java_library { host_supported: true, srcs: [ "core/java/android/annotation/IntDef.java", - "core/java/android/annotation/UnsupportedAppUsage.java", ], static_libs: [ "art.module.api.annotations", @@ -612,6 +612,14 @@ genrule { out: ["android/util/StatsLogInternal.java"], } +genrule { + name: "statslog-framework-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module framework" + + " --javaPackage com.android.internal.util --javaClass FrameworkStatsLog --worksource", + out: ["com/android/internal/util/FrameworkStatsLog.java"], +} + gensrcs { name: "framework-javastream-protos", depfile: true, @@ -655,7 +663,6 @@ filegroup { "core/java/android/annotation/SystemApi.java", "core/java/android/annotation/SystemService.java", "core/java/android/annotation/TestApi.java", - "core/java/android/annotation/UnsupportedAppUsage.java", "core/java/com/android/internal/annotations/GuardedBy.java", "core/java/com/android/internal/annotations/VisibleForTesting.java", "core/java/com/android/internal/annotations/Immutable.java", @@ -930,6 +937,8 @@ filegroup { srcs: [ "core/java/android/os/incremental/IIncrementalManager.aidl", "core/java/android/os/incremental/IIncrementalManagerNative.aidl", + "core/java/android/os/incremental/IncrementalNewFileParams.aidl", + "core/java/android/os/incremental/IncrementalSignature.aidl", ], path: "core/java", } @@ -1062,7 +1071,6 @@ java_library { "core/java/android/annotation/Nullable.java", "core/java/android/annotation/SystemApi.java", "core/java/android/annotation/TestApi.java", - "core/java/android/annotation/UnsupportedAppUsage.java", "core/java/android/os/HidlMemory.java", "core/java/android/os/HwBinder.java", "core/java/android/os/HwBlob.java", diff --git a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java index 2955d2ca7d0e..050fecde8213 100644 --- a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java +++ b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java @@ -80,7 +80,7 @@ public class ResourcesManagerPerfTest { private void getResourcesForPath(String path) { ResourcesManager.getInstance().getResources(null, path, null, null, null, Display.DEFAULT_DISPLAY, null, sContext.getResources().getCompatibilityInfo(), - null); + null, null); } @Test diff --git a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java index 6123e69b584e..f4c0a172710b 100644 --- a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java +++ b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java @@ -96,7 +96,7 @@ public class ResourcesThemePerfTest { Resources destResources = resourcesManager.getResources(null, ai.sourceDir, ai.splitSourceDirs, ai.resourceDirs, ai.sharedLibraryFiles, Display.DEFAULT_DISPLAY, - c, mContext.getResources().getCompatibilityInfo(), null); + c, mContext.getResources().getCompatibilityInfo(), null, null); Assert.assertNotEquals(destResources.getAssets(), mContext.getAssets()); Resources.Theme destTheme = destResources.newTheme(); diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java index c62aad622f25..b6e39e14602a 100644 --- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java @@ -150,7 +150,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame, mOutContentInsets, mOutVisibleInsets, mOutStableInsets, mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration, - mOutSurfaceControl, mOutInsetsState, new Point()); + mOutSurfaceControl, mOutInsetsState, new Point(), new SurfaceControl()); } } } diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 18382a488428..2266d049d70a 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -50,7 +50,7 @@ java_library { ], static_libs: [ - "exoplayer2-core" + "exoplayer2-extractor" ], jarjar_rules: "jarjar_rules.txt", diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java index 1dbad456760c..90b1c4b6ff5f 100644 --- a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java +++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistenceImpl.java @@ -19,6 +19,7 @@ package com.android.permission.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ApexContext; +import android.content.pm.PackageManager; import android.os.UserHandle; import android.util.ArrayMap; import android.util.AtomicFile; @@ -242,9 +243,10 @@ public class RuntimePermissionsPersistenceImpl implements RuntimePermissionsPers serializer.startTag(null, TAG_PERMISSION); serializer.attribute(null, ATTRIBUTE_NAME, permissionState.getName()); serializer.attribute(null, ATTRIBUTE_GRANTED, Boolean.toString( - permissionState.isGranted())); + permissionState.isGranted() && (permissionState.getFlags() + & PackageManager.FLAG_PERMISSION_ONE_TIME) == 0)); serializer.attribute(null, ATTRIBUTE_FLAGS, Integer.toHexString( - permissionState.getFlags())); + permissionState.getFlags() & ~PackageManager.FLAG_PERMISSION_ONE_TIME)); serializer.endTag(null, TAG_PERMISSION); } } diff --git a/apex/sdkextensions/OWNERS b/apex/sdkextensions/OWNERS index feb274262bef..a6e55228596b 100644 --- a/apex/sdkextensions/OWNERS +++ b/apex/sdkextensions/OWNERS @@ -1 +1,2 @@ +dariofreni@google.com hansson@google.com diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java index ad1ac95d667c..a1de330c300a 100644 --- a/apex/statsd/framework/java/android/app/StatsManager.java +++ b/apex/statsd/framework/java/android/app/StatsManager.java @@ -474,17 +474,6 @@ public final class StatsManager { } /** - * Temp registration for while the migration is in progress. - * - * @hide - */ - public void registerPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata, - @NonNull StatsPullAtomCallback callback, - @NonNull @CallbackExecutor Executor executor) { - registerPullAtomCallback(atomTag, metadata, executor, callback); - } - - /** * 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. @@ -591,15 +580,6 @@ public final class StatsManager { } /** - * Temp for while migrations are in progress. - * - * @hide - */ - public static PullAtomMetadata.Builder newBuilder() { - return new PullAtomMetadata.Builder(); - } - - /** * Builder for PullAtomMetadata. */ public static class Builder { diff --git a/apex/statsd/tests/libstatspull/Android.bp b/apex/statsd/tests/libstatspull/Android.bp new file mode 100644 index 000000000000..e81396405d31 --- /dev/null +++ b/apex/statsd/tests/libstatspull/Android.bp @@ -0,0 +1,56 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "LibStatsPullTests", + static_libs: [ + "androidx.test.rules", + "platformprotoslite", + "statsdprotolite", + "truth-prebuilt", + ], + libs: [ + "android.test.runner.stubs", + "android.test.base.stubs", + ], + jni_libs: [ + "libstatspull_testhelper", + ], + srcs: [ + "src/**/*.java", + "protos/**/*.proto", + ], + test_suites: [ + "general-tests", + ], + platform_apis: true, + privileged: true, + certificate: "platform", + compile_multilib: "both", +} + +cc_library_shared { + name: "libstatspull_testhelper", + srcs: ["jni/stats_pull_helper.cpp"], + cflags: [ + "-Wall", + "-Werror", + ], + shared_libs: [ + "libbinder", + "libutils", + "libstatspull", + "libstatssocket", + ], +}
\ No newline at end of file diff --git a/apex/statsd/tests/libstatspull/AndroidManifest.xml b/apex/statsd/tests/libstatspull/AndroidManifest.xml new file mode 100644 index 000000000000..bffd400bdb2c --- /dev/null +++ b/apex/statsd/tests/libstatspull/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?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.internal.os.statsd.libstats" > + + + <uses-permission android:name="android.permission.DUMP" /> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.internal.os.statsd.libstats" + android:label="Tests for libstatspull"> + </instrumentation> +</manifest> + diff --git a/apex/statsd/tests/libstatspull/TEST_MAPPING b/apex/statsd/tests/libstatspull/TEST_MAPPING new file mode 100644 index 000000000000..5e1178cffc05 --- /dev/null +++ b/apex/statsd/tests/libstatspull/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit" : [ + { + "name" : "LibStatsPullTests" + } + ] +}
\ No newline at end of file diff --git a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp new file mode 100644 index 000000000000..e4ab823f345a --- /dev/null +++ b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp @@ -0,0 +1,88 @@ +/* + * 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. + */ + +#include <binder/ProcessState.h> +#include <jni.h> +#include <log/log.h> +#include <stats_event.h> +#include <stats_pull_atom_callback.h> + +#include <chrono> +#include <thread> + +using std::this_thread::sleep_for; +using namespace android; + +namespace { +static int32_t sAtomTag; +static int32_t sPullReturnVal; +static int64_t sLatencyMillis; +static int32_t sAtomsPerPull; +static int32_t sNumPulls = 0; + +static bool initialized = false; + +static void init() { + if (!initialized) { + initialized = true; + // Set up the binder + sp<ProcessState> ps(ProcessState::self()); + ps->setThreadPoolMaxThreadCount(9); + ps->startThreadPool(); + ps->giveThreadPoolName(); + } +} + +static status_pull_atom_return_t pullAtomCallback(int32_t atomTag, pulled_stats_event_list* 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); + } + return sPullReturnVal; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_registerStatsPuller( + JNIEnv* /*env*/, jobject /* this */, jint atomTag, jlong timeoutNs, jlong coolDownNs, + jint pullRetVal, jlong latencyMillis, int atomsPerPull) +{ + init(); + sAtomTag = atomTag; + sPullReturnVal = pullRetVal; + 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); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_unregisterStatsPuller( + JNIEnv* /*env*/, jobject /* this */, jint /*atomTag*/) +{ + unregister_stats_pull_atom_callback(sAtomTag); +} +} // namespace
\ No newline at end of file diff --git a/apex/statsd/tests/libstatspull/protos/test_atoms.proto b/apex/statsd/tests/libstatspull/protos/test_atoms.proto new file mode 100644 index 000000000000..56c1b534a7ce --- /dev/null +++ b/apex/statsd/tests/libstatspull/protos/test_atoms.proto @@ -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. + */ +syntax = "proto2"; + +package com.android.internal.os.statsd.protos; + +option java_package = "com.android.internal.os.statsd.protos"; +option java_outer_classname = "TestAtoms"; + +message PullCallbackAtomWrapper { + optional PullCallbackAtom pull_callback_atom = 150030; +} + +message PullCallbackAtom { + optional int64 long_val = 1; +} + + + diff --git a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/StatsConfigUtils.java b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/StatsConfigUtils.java new file mode 100644 index 000000000000..d0d140092586 --- /dev/null +++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/StatsConfigUtils.java @@ -0,0 +1,124 @@ +/* + * 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.internal.os.statsd; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.StatsManager; +import android.util.Log; + +import com.android.internal.os.StatsdConfigProto.AtomMatcher; +import com.android.internal.os.StatsdConfigProto.FieldValueMatcher; +import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; +import com.android.internal.os.StatsdConfigProto.StatsdConfig; +import com.android.os.AtomsProto.AppBreadcrumbReported; +import com.android.os.AtomsProto.Atom; +import com.android.os.StatsLog.ConfigMetricsReport; +import com.android.os.StatsLog.ConfigMetricsReportList; +import com.android.os.StatsLog.GaugeBucketInfo; +import com.android.os.StatsLog.GaugeMetricData; +import com.android.os.StatsLog.StatsLogReport; +import com.android.os.StatsLog.StatsdStatsReport; +import com.android.os.StatsLog.StatsdStatsReport.ConfigStats; + +import java.util.ArrayList; +import java.util.List; + +/** + * Util class for constructing statsd configs. + */ +public class StatsConfigUtils { + public static final String TAG = "statsd.StatsConfigUtils"; + public static final int SHORT_WAIT = 2_000; // 2 seconds. + + /** + * @return An empty StatsdConfig in serialized proto format. + */ + public static StatsdConfig.Builder getSimpleTestConfig(long configId) { + return StatsdConfig.newBuilder().setId(configId) + .addAllowedLogSource(StatsConfigUtils.class.getPackage().getName()); + } + + + public static boolean verifyValidConfigExists(StatsManager statsManager, long configId) { + StatsdStatsReport report = null; + try { + report = StatsdStatsReport.parser().parseFrom(statsManager.getStatsMetadata()); + } catch (Exception e) { + Log.e(TAG, "getMetadata failed", e); + } + assertThat(report).isNotNull(); + boolean foundConfig = false; + for (ConfigStats configStats : report.getConfigStatsList()) { + if (configStats.getId() == configId && configStats.getIsValid() + && configStats.getDeletionTimeSec() == 0) { + foundConfig = true; + } + } + return foundConfig; + } + + public static AtomMatcher getAppBreadcrumbMatcher(long id, int label) { + return AtomMatcher.newBuilder() + .setId(id) + .setSimpleAtomMatcher( + SimpleAtomMatcher.newBuilder() + .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER) + .addFieldValueMatcher(FieldValueMatcher.newBuilder() + .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER) + .setEqInt(label) + ) + ) + .build(); + } + + public static ConfigMetricsReport getConfigMetricsReport(StatsManager statsManager, + long configId) { + ConfigMetricsReportList reportList = null; + try { + reportList = ConfigMetricsReportList.parser() + .parseFrom(statsManager.getReports(configId)); + } catch (Exception e) { + Log.e(TAG, "getData failed", e); + } + assertThat(reportList).isNotNull(); + assertThat(reportList.getReportsCount()).isEqualTo(1); + ConfigMetricsReport report = reportList.getReports(0); + assertThat(report.getDumpReportReason()) + .isEqualTo(ConfigMetricsReport.DumpReportReason.GET_DATA_CALLED); + return report; + + } + public static List<Atom> getGaugeMetricDataList(ConfigMetricsReport report) { + List<Atom> data = new ArrayList<>(); + for (StatsLogReport metric : report.getMetricsList()) { + for (GaugeMetricData gaugeMetricData : metric.getGaugeMetrics().getDataList()) { + for (GaugeBucketInfo bucketInfo : gaugeMetricData.getBucketInfoList()) { + for (Atom atom : bucketInfo.getAtomList()) { + data.add(atom); + } + } + } + } + return data; + } + + public static List<Atom> getGaugeMetricDataList(StatsManager statsManager, long configId) { + ConfigMetricsReport report = getConfigMetricsReport(statsManager, configId); + return getGaugeMetricDataList(report); + } +} + 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 new file mode 100644 index 000000000000..dbd636d2e95c --- /dev/null +++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java @@ -0,0 +1,285 @@ +/* + * 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.internal.os.statsd.libstats; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.StatsManager; +import android.content.Context; +import android.util.Log; +import android.util.StatsLog; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.StatsdConfigProto.AtomMatcher; +import com.android.internal.os.StatsdConfigProto.FieldFilter; +import com.android.internal.os.StatsdConfigProto.GaugeMetric; +import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; +import com.android.internal.os.StatsdConfigProto.StatsdConfig; +import com.android.internal.os.StatsdConfigProto.TimeUnit; +import com.android.internal.os.statsd.StatsConfigUtils; +import com.android.internal.os.statsd.protos.TestAtoms; +import com.android.os.AtomsProto.Atom; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +/** + * Test puller registration. + */ +@MediumTest +@RunWith(AndroidJUnit4.class) +public class LibStatsPullTests { + private static final String LOG_TAG = LibStatsPullTests.class.getSimpleName(); + private static final int SHORT_SLEEP_MILLIS = 250; + private static final int LONG_SLEEP_MILLIS = 1_000; + private Context mContext; + private static final int PULL_ATOM_TAG = 150030; + private static final int APP_BREADCRUMB_LABEL = 3; + private static int sPullReturnValue; + private static long sConfigId; + private static long sPullLatencyMillis; + private static long sPullTimeoutNs; + private static long sCoolDownNs; + private static int sAtomsPerPull; + + static { + System.loadLibrary("statspull_testhelper"); + } + + /** + * Setup the tests. Initialize shared data. + */ + @Before + public void setup() { +// Debug.waitForDebugger(); + mContext = InstrumentationRegistry.getTargetContext(); + assertThat(InstrumentationRegistry.getInstrumentation()).isNotNull(); + sPullReturnValue = StatsManager.PULL_SUCCESS; + sPullLatencyMillis = 0; + sPullTimeoutNs = 10_000_000_000L; + sCoolDownNs = 1_000_000_000L; + sAtomsPerPull = 1; + } + + /** + * Teardown the tests. + */ + @After + public void tearDown() throws Exception { + unregisterStatsPuller(PULL_ATOM_TAG); + StatsManager statsManager = (StatsManager) mContext.getSystemService( + Context.STATS_MANAGER); + statsManager.removeConfig(sConfigId); + } + + /** + * Tests adding a puller callback and that pulls complete successfully. + */ + @Test + public void testPullAtomCallbackRegistration() throws Exception { + StatsManager statsManager = (StatsManager) mContext.getSystemService( + Context.STATS_MANAGER); + // Upload a config that captures that pulled atom. + createAndAddConfigToStatsd(statsManager); + + // Add the puller. + registerStatsPuller(PULL_ATOM_TAG, sPullTimeoutNs, sCoolDownNs, sPullReturnValue, + sPullLatencyMillis, sAtomsPerPull); + Thread.sleep(SHORT_SLEEP_MILLIS); + StatsLog.logStart(APP_BREADCRUMB_LABEL); + // Let the current bucket finish. + Thread.sleep(LONG_SLEEP_MILLIS); + List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId); + unregisterStatsPuller(PULL_ATOM_TAG); + assertThat(data.size()).isEqualTo(1); + TestAtoms.PullCallbackAtomWrapper atomWrapper = null; + try { + atomWrapper = TestAtoms.PullCallbackAtomWrapper.parser() + .parseFrom(data.get(0).toByteArray()); + } catch (Exception e) { + Log.e(LOG_TAG, "Failed to parse primitive atoms"); + } + assertThat(atomWrapper).isNotNull(); + assertThat(atomWrapper.hasPullCallbackAtom()).isTrue(); + TestAtoms.PullCallbackAtom atom = + atomWrapper.getPullCallbackAtom(); + assertThat(atom.getLongVal()).isEqualTo(1); + } + + /** + * Tests that a failed pull is skipped. + */ + @Test + public void testPullAtomCallbackFailure() throws Exception { + StatsManager statsManager = (StatsManager) mContext.getSystemService( + Context.STATS_MANAGER); + createAndAddConfigToStatsd(statsManager); + sPullReturnValue = StatsManager.PULL_SKIP; + // Add the puller. + registerStatsPuller(PULL_ATOM_TAG, sPullTimeoutNs, sCoolDownNs, sPullReturnValue, + sPullLatencyMillis, sAtomsPerPull); + Thread.sleep(SHORT_SLEEP_MILLIS); + StatsLog.logStart(APP_BREADCRUMB_LABEL); + // Let the current bucket finish. + Thread.sleep(LONG_SLEEP_MILLIS); + List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId); + unregisterStatsPuller(PULL_ATOM_TAG); + assertThat(data.size()).isEqualTo(0); + } + + /** + * Tests that a pull that times out is skipped. + */ + @Test + public void testPullAtomCallbackTimeout() throws Exception { + StatsManager statsManager = (StatsManager) mContext.getSystemService( + Context.STATS_MANAGER); + createAndAddConfigToStatsd(statsManager); + // The puller will sleep for 1.5 sec. + sPullLatencyMillis = 1_500; + // 1 second timeout + sPullTimeoutNs = 1_000_000_000; + + // Add the puller. + registerStatsPuller(PULL_ATOM_TAG, sPullTimeoutNs, sCoolDownNs, sPullReturnValue, + sPullLatencyMillis, sAtomsPerPull); + Thread.sleep(SHORT_SLEEP_MILLIS); + StatsLog.logStart(APP_BREADCRUMB_LABEL); + // Let the current bucket finish and the pull timeout. + Thread.sleep(sPullLatencyMillis * 2); + List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId); + unregisterStatsPuller(PULL_ATOM_TAG); + assertThat(data.size()).isEqualTo(0); + } + + /** + * Tests that 2 pulls in quick succession use the cache instead of pulling again. + */ + @Test + public void testPullAtomCallbackCache() throws Exception { + StatsManager statsManager = (StatsManager) mContext.getSystemService( + Context.STATS_MANAGER); + createAndAddConfigToStatsd(statsManager); + + // Set the cooldown to 10 seconds + sCoolDownNs = 10_000_000_000L; + // Add the puller. + registerStatsPuller(PULL_ATOM_TAG, sPullTimeoutNs, sCoolDownNs, sPullReturnValue, + sPullLatencyMillis, sAtomsPerPull); + + Thread.sleep(SHORT_SLEEP_MILLIS); + StatsLog.logStart(APP_BREADCRUMB_LABEL); + // Pull from cache. + StatsLog.logStart(APP_BREADCRUMB_LABEL); + Thread.sleep(LONG_SLEEP_MILLIS); + List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId); + unregisterStatsPuller(PULL_ATOM_TAG); + assertThat(data.size()).isEqualTo(2); + for (int i = 0; i < data.size(); i++) { + TestAtoms.PullCallbackAtomWrapper atomWrapper = null; + try { + atomWrapper = TestAtoms.PullCallbackAtomWrapper.parser() + .parseFrom(data.get(i).toByteArray()); + } catch (Exception e) { + Log.e(LOG_TAG, "Failed to parse primitive atoms"); + } + assertThat(atomWrapper).isNotNull(); + assertThat(atomWrapper.hasPullCallbackAtom()).isTrue(); + TestAtoms.PullCallbackAtom atom = + atomWrapper.getPullCallbackAtom(); + assertThat(atom.getLongVal()).isEqualTo(1); + } + } + + /** + * Tests that a pull that returns 1000 stats events works properly. + */ + @Test + public void testPullAtomCallbackStress() throws Exception { + StatsManager statsManager = (StatsManager) mContext.getSystemService( + Context.STATS_MANAGER); + // Upload a config that captures that pulled atom. + createAndAddConfigToStatsd(statsManager); + sAtomsPerPull = 1000; + // Add the puller. + registerStatsPuller(PULL_ATOM_TAG, sPullTimeoutNs, sCoolDownNs, sPullReturnValue, + sPullLatencyMillis, sAtomsPerPull); + + Thread.sleep(SHORT_SLEEP_MILLIS); + StatsLog.logStart(APP_BREADCRUMB_LABEL); + // Let the current bucket finish. + Thread.sleep(LONG_SLEEP_MILLIS); + List<Atom> data = StatsConfigUtils.getGaugeMetricDataList(statsManager, sConfigId); + statsManager.unregisterPullAtomCallback(PULL_ATOM_TAG); + assertThat(data.size()).isEqualTo(sAtomsPerPull); + + for (int i = 0; i < data.size(); i++) { + TestAtoms.PullCallbackAtomWrapper atomWrapper = null; + try { + atomWrapper = TestAtoms.PullCallbackAtomWrapper.parser() + .parseFrom(data.get(i).toByteArray()); + } catch (Exception e) { + Log.e(LOG_TAG, "Failed to parse primitive atoms"); + } + assertThat(atomWrapper).isNotNull(); + assertThat(atomWrapper.hasPullCallbackAtom()).isTrue(); + TestAtoms.PullCallbackAtom atom = + atomWrapper.getPullCallbackAtom(); + assertThat(atom.getLongVal()).isEqualTo(1); + } + } + + private void createAndAddConfigToStatsd(StatsManager statsManager) throws Exception { + sConfigId = System.currentTimeMillis(); + long triggerMatcherId = sConfigId + 10; + long pullerMatcherId = sConfigId + 11; + long metricId = sConfigId + 100; + StatsdConfig config = StatsConfigUtils.getSimpleTestConfig(sConfigId) + .addAtomMatcher( + StatsConfigUtils.getAppBreadcrumbMatcher(triggerMatcherId, + APP_BREADCRUMB_LABEL)) + .addAtomMatcher(AtomMatcher.newBuilder() + .setId(pullerMatcherId) + .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder() + .setAtomId(PULL_ATOM_TAG)) + ) + .addGaugeMetric(GaugeMetric.newBuilder() + .setId(metricId) + .setWhat(pullerMatcherId) + .setTriggerEvent(triggerMatcherId) + .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true)) + .setBucket(TimeUnit.CTS) + .setSamplingType(GaugeMetric.SamplingType.FIRST_N_SAMPLES) + .setMaxNumGaugeAtomsPerBucket(1000) + ) + .build(); + statsManager.addConfig(sConfigId, config.toByteArray()); + assertThat(StatsConfigUtils.verifyValidConfigExists(statsManager, sConfigId)).isTrue(); + } + + private native void registerStatsPuller(int atomTag, long timeoutNs, long coolDownNs, + int pullReturnVal, long latencyMillis, int atomPerPull); + + private native void unregisterStatsPuller(int atomTag); +} + diff --git a/api/current.txt b/api/current.txt index 462a3f4d20af..9c7a547d1c99 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2873,6 +2873,12 @@ package android.accessibilityservice { 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>); + 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_TRIPLE_TAP = 21; // 0x15 + field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17 + field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16 + field public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; // 0x18 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 @@ -12653,8 +12659,8 @@ package android.content.res { public class Resources { ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration); - method public void addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider, @IntRange(from=0) int); - method public int addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider); + method public void addLoader(@NonNull android.content.res.loader.ResourcesLoader); + method public void clearLoaders(); method public final void finishPreloading(); method public final void flushLayoutCache(); method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimRes @AnimatorRes int) throws android.content.res.Resources.NotFoundException; @@ -12681,7 +12687,7 @@ package android.content.res { method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException; method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException; method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException; - method @NonNull public java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>> getLoaders(); + method @NonNull public java.util.List<android.content.res.loader.ResourcesLoader> getLoaders(); method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException; method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException; method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException; @@ -12709,8 +12715,8 @@ package android.content.res { method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException; method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException; method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; - method public int removeLoader(@NonNull android.content.res.loader.ResourceLoader); - method public void setLoaders(@Nullable java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>>); + method public void removeLoader(@NonNull android.content.res.loader.ResourcesLoader); + method public void setLoaders(@NonNull java.util.List<android.content.res.loader.ResourcesLoader>); method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics); field @AnyRes public static final int ID_NULL = 0; // 0x0 } @@ -12780,27 +12786,37 @@ package android.content.res { package android.content.res.loader { - public class DirectoryResourceLoader implements android.content.res.loader.ResourceLoader { - ctor public DirectoryResourceLoader(@NonNull java.io.File); + public interface AssetsProvider { + method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException; + method @Nullable public default android.os.ParcelFileDescriptor loadAssetParcelFd(@NonNull String) throws java.io.IOException; + } + + public class DirectoryAssetsProvider implements android.content.res.loader.AssetsProvider { + ctor public DirectoryAssetsProvider(@NonNull java.io.File); method @Nullable public java.io.File findFile(@NonNull String); method @NonNull public java.io.File getDirectory(); } - public interface ResourceLoader { - method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException; - method @Nullable public default android.os.ParcelFileDescriptor loadAssetFd(@NonNull String) throws java.io.IOException; - method @Nullable public default android.graphics.drawable.Drawable loadDrawable(@NonNull android.util.TypedValue, int, int, @Nullable android.content.res.Resources.Theme); - method @Nullable public default android.content.res.XmlResourceParser loadXmlResourceParser(@NonNull String, @AnyRes int); + public class ResourcesLoader { + ctor public ResourcesLoader(); + method public void addProvider(@NonNull android.content.res.loader.ResourcesProvider); + method public void clearProviders(); + method @NonNull public java.util.List<android.content.res.loader.ResourcesProvider> getProviders(); + method public void removeProvider(@NonNull android.content.res.loader.ResourcesProvider); + method public void setProviders(@NonNull java.util.List<android.content.res.loader.ResourcesProvider>); } - public final class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable { + public class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable { method public void close(); - method @NonNull public static android.content.res.loader.ResourcesProvider empty(); + method @NonNull public static android.content.res.loader.ResourcesProvider empty(@NonNull android.content.res.loader.AssetsProvider); + method @Nullable public android.content.res.loader.AssetsProvider getAssetsProvider(); method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException; + method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException; method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory) throws java.io.IOException; - method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException; - method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.SharedMemory) throws java.io.IOException; + method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException; method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException; + method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException; + method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.SharedMemory, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException; } } @@ -14249,9 +14265,12 @@ package android.graphics { method public int getWidth(); method public boolean isHardwareAccelerated(); method public boolean isOpaque(); - method public boolean quickReject(@NonNull android.graphics.RectF, @NonNull android.graphics.Canvas.EdgeType); - method public boolean quickReject(@NonNull android.graphics.Path, @NonNull android.graphics.Canvas.EdgeType); - method public boolean quickReject(float, float, float, float, @NonNull android.graphics.Canvas.EdgeType); + method @Deprecated public boolean quickReject(@NonNull android.graphics.RectF, @NonNull android.graphics.Canvas.EdgeType); + method public boolean quickReject(@NonNull android.graphics.RectF); + method @Deprecated public boolean quickReject(@NonNull android.graphics.Path, @NonNull android.graphics.Canvas.EdgeType); + method public boolean quickReject(@NonNull android.graphics.Path); + method @Deprecated public boolean quickReject(float, float, float, float, @NonNull android.graphics.Canvas.EdgeType); + method public boolean quickReject(float, float, float, float); method public void restore(); method public void restoreToCount(int); method public void rotate(float); @@ -14276,9 +14295,9 @@ package android.graphics { field public static final int ALL_SAVE_FLAG = 31; // 0x1f } - public enum Canvas.EdgeType { - enum_constant public static final android.graphics.Canvas.EdgeType AA; - enum_constant public static final android.graphics.Canvas.EdgeType BW; + @Deprecated public enum Canvas.EdgeType { + enum_constant @Deprecated public static final android.graphics.Canvas.EdgeType AA; + enum_constant @Deprecated public static final android.graphics.Canvas.EdgeType BW; } public enum Canvas.VertexMode { @@ -15063,7 +15082,7 @@ package android.graphics { method public void cubicTo(float, float, float, float, float, float); method @NonNull public android.graphics.Path.FillType getFillType(); method public void incReserve(int); - method public boolean isConvex(); + method @Deprecated public boolean isConvex(); method public boolean isEmpty(); method public boolean isInverseFillType(); method public boolean isRect(@Nullable android.graphics.RectF); @@ -26701,7 +26720,6 @@ package android.media { method public int getVolume(); method public int getVolumeHandling(); method public int getVolumeMax(); - method public boolean hasAnyFeatures(@NonNull java.util.Collection<java.lang.String>); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2 field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1 @@ -29200,6 +29218,7 @@ package android.media.tv { method public void onAppPrivateCommand(@NonNull String, android.os.Bundle); method public abstract void onRelease(); method public abstract void onStartRecording(@Nullable android.net.Uri); + method public void onStartRecording(@Nullable android.net.Uri, @NonNull android.os.Bundle); method public abstract void onStopRecording(); method public abstract void onTune(android.net.Uri); method public void onTune(android.net.Uri, android.os.Bundle); @@ -29250,6 +29269,7 @@ package android.media.tv { method public void release(); method public void sendAppPrivateCommand(@NonNull String, android.os.Bundle); method public void startRecording(@Nullable android.net.Uri); + method public void startRecording(@Nullable android.net.Uri, @NonNull android.os.Bundle); method public void stopRecording(); method public void tune(String, android.net.Uri); method public void tune(String, android.net.Uri, android.os.Bundle); @@ -36483,7 +36503,7 @@ package android.os { method public boolean isSustainedPerformanceModeSupported(); method public boolean isWakeLockLevelSupported(int); method public android.os.PowerManager.WakeLock newWakeLock(int, String); - method public void reboot(@Nullable String); + method @RequiresPermission(android.Manifest.permission.REBOOT) public void reboot(@Nullable String); method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener); field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000 field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED"; @@ -36925,6 +36945,7 @@ package android.os { method public static android.os.VibrationEffect createWaveform(long[], int); method public static android.os.VibrationEffect createWaveform(long[], int[], int); method public int describeContents(); + method @NonNull public static android.os.VibrationEffect.Composition startComposition(); field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR; field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff field public static final int EFFECT_CLICK = 0; // 0x0 @@ -36933,7 +36954,26 @@ package android.os { field public static final int EFFECT_TICK = 2; // 0x2 } + public static class VibrationEffect.Composition { + ctor public VibrationEffect.Composition(); + method @Nullable public android.os.VibrationEffect.Composition addPrimitive(int); + method @Nullable public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float); + method @Nullable public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int); + method @NonNull public android.os.VibrationEffect compose(); + field public static final int PRIMITIVE_CLICK = 1; // 0x1 + field public static final int PRIMITIVE_LIGHT_TICK = 7; // 0x7 + field public static final int PRIMITIVE_QUICK_FALL = 6; // 0x6 + field public static final int PRIMITIVE_QUICK_RISE = 4; // 0x4 + field public static final int PRIMITIVE_SLOW_RISE = 5; // 0x5 + field public static final int PRIMITIVE_SPIN = 3; // 0x3 + field public static final int PRIMITIVE_THUD = 2; // 0x2 + } + public abstract class Vibrator { + method @Nullable public Boolean areAllEffectsSupported(@NonNull int...); + method public boolean areAllPrimitivesSupported(@NonNull int...); + method @Nullable public boolean[] areEffectsSupported(@NonNull int...); + method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...); method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); method public abstract boolean hasAmplitudeControl(); method public abstract boolean hasVibrator(); @@ -43868,11 +43908,13 @@ package android.service.quicksettings { method public android.graphics.drawable.Icon getIcon(); method public CharSequence getLabel(); method public int getState(); + method @Nullable public CharSequence getStateDescription(); method @Nullable public CharSequence getSubtitle(); method public void setContentDescription(CharSequence); method public void setIcon(android.graphics.drawable.Icon); method public void setLabel(CharSequence); method public void setState(int); + method public void setStateDescription(@Nullable CharSequence); method public void setSubtitle(@Nullable CharSequence); method public void updateTile(); method public void writeToParcel(android.os.Parcel, int); @@ -47049,6 +47091,7 @@ package android.telephony { method @Nullable public android.telephony.CellIdentity getCellIdentity(); method public int getDomain(); method public int getNrState(); + method @NonNull public String getRegisteredPlmn(); method public int getTransportType(); method public boolean isRegistered(); method public boolean isRoaming(); @@ -54139,7 +54182,7 @@ package android.view { public class ViewConfiguration { ctor @Deprecated public ViewConfiguration(); method public static android.view.ViewConfiguration get(android.content.Context); - method @FloatRange(from=1.0) public static float getAmbiguousGestureMultiplier(); + method @Deprecated @FloatRange(from=1.0) public static float getAmbiguousGestureMultiplier(); method public static long getDefaultActionModeHideDuration(); method public static int getDoubleTapTimeout(); method @Deprecated public static int getEdgeSlop(); @@ -54153,6 +54196,7 @@ package android.view { method @Deprecated public static int getMaximumFlingVelocity(); method @Deprecated public static int getMinimumFlingVelocity(); method public static int getPressedStateDuration(); + method @FloatRange(from=1.0) public float getScaledAmbiguousGestureMultiplier(); method public int getScaledDoubleTapSlop(); method public int getScaledEdgeSlop(); method public int getScaledFadingEdgeLength(); diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 6f4a27ecfb0b..1a2cb74a5aad 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -19,7 +19,7 @@ package android.app.timedetector { method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder setUtcTime(@Nullable android.os.TimestampedValue<java.lang.Long>); } - public class TimeDetector { + public interface TimeDetector { method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTime(@NonNull android.app.timedetector.PhoneTimeSuggestion); } @@ -59,7 +59,7 @@ package android.app.timezonedetector { method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setZoneId(@Nullable String); } - public class TimeZoneDetector { + public interface TimeZoneDetector { method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTimeZone(@NonNull android.app.timezonedetector.PhoneTimeZoneSuggestion); } @@ -102,18 +102,18 @@ package android.timezone { method @NonNull public String getTimeZoneId(); } - public class TelephonyLookup { + public final class TelephonyLookup { method @NonNull public static android.timezone.TelephonyLookup getInstance(); method @Nullable public android.timezone.TelephonyNetworkFinder getTelephonyNetworkFinder(); } - public class TelephonyNetwork { + public final class TelephonyNetwork { method @NonNull public String getCountryIsoCode(); method @NonNull public String getMcc(); method @NonNull public String getMnc(); } - public class TelephonyNetworkFinder { + public final class TelephonyNetworkFinder { method @Nullable public android.timezone.TelephonyNetwork findNetworkByMccMnc(@NonNull String, @NonNull String); } @@ -134,7 +134,7 @@ package android.timezone { method @NonNull public static android.timezone.TzDataSetVersion read() throws java.io.IOException, android.timezone.TzDataSetVersion.TzDataSetException; } - public static class TzDataSetVersion.TzDataSetException extends java.lang.Exception { + public static final class TzDataSetVersion.TzDataSetException extends java.lang.Exception { ctor public TzDataSetVersion.TzDataSetException(String); ctor public TzDataSetVersion.TzDataSetException(String, Throwable); } diff --git a/api/system-current.txt b/api/system-current.txt index 1a039dc3ce09..6b84a50ebebd 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -8367,7 +8367,7 @@ package android.os { method @NonNull public android.os.BatterySaverPolicyConfig.Builder setLocationMode(int); } - public class BatteryStatsManager { + public final class BatteryStatsManager { method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.os.connectivity.CellularBatteryStats getCellularBatteryStats(); method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.os.connectivity.WifiBatteryStats getWifiBatteryStats(); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource); @@ -9703,6 +9703,7 @@ package android.provider { field public static final String CMAS_SEVERITY = "cmas_severity"; field public static final String CMAS_URGENCY = "cmas_urgency"; field @NonNull public static final android.net.Uri CONTENT_URI; + field public static final String DATA_CODING_SCHEME = "dcs"; field public static final String DEFAULT_SORT_ORDER = "date DESC"; field public static final String DELIVERY_TIME = "date"; field public static final String ETWS_WARNING_TYPE = "etws_warning_type"; @@ -9710,9 +9711,11 @@ package android.provider { field public static final String GEOMETRIES = "geometries"; field public static final String LAC = "lac"; field public static final String LANGUAGE_CODE = "language"; + field public static final String LOCATION_CHECK_TIME = "location_check_time"; field public static final String MAXIMUM_WAIT_TIME = "maximum_wait_time"; field public static final String MESSAGE_BODY = "body"; field public static final String MESSAGE_BROADCASTED = "message_broadcasted"; + field public static final String MESSAGE_DISPLAYED = "message_displayed"; field public static final String MESSAGE_FORMAT = "format"; field @NonNull @RequiresPermission(android.Manifest.permission.READ_CELL_BROADCASTS) public static final android.net.Uri MESSAGE_HISTORY_URI; field public static final String MESSAGE_PRIORITY = "priority"; @@ -11175,10 +11178,12 @@ package android.telephony { public class CellBroadcastIntents { method public static void sendSmsCbReceivedBroadcast(@NonNull android.content.Context, @Nullable android.os.UserHandle, @NonNull android.telephony.SmsCbMessage, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, int); + field public static final String ACTION_AREA_INFO_UPDATED = "android.telephony.action.AREA_INFO_UPDATED"; } public abstract class CellBroadcastService extends android.app.Service { ctor public CellBroadcastService(); + method @NonNull @WorkerThread public abstract CharSequence getCellBroadcastAreaInfo(int); method @CallSuper public android.os.IBinder onBind(@Nullable android.content.Intent); method public abstract void onCdmaCellBroadcastSms(int, @NonNull byte[], int); method public abstract void onCdmaScpMessage(int, @NonNull java.util.List<android.telephony.cdma.CdmaSmsCbProgramData>, @NonNull String, @NonNull java.util.function.Consumer<android.os.Bundle>); @@ -11726,6 +11731,7 @@ package android.telephony { method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setCellIdentity(@Nullable android.telephony.CellIdentity); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setDomain(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setEmergencyOnly(boolean); + method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegisteredPlmn(@Nullable String); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegistrationState(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRejectCause(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setTransportType(int); @@ -12047,11 +12053,12 @@ package android.telephony { } public final class SmsCbMessage implements android.os.Parcelable { - ctor public SmsCbMessage(int, int, int, @NonNull android.telephony.SmsCbLocation, int, @Nullable String, @Nullable String, int, @Nullable android.telephony.SmsCbEtwsInfo, @Nullable android.telephony.SmsCbCmasInfo, int, @Nullable java.util.List<android.telephony.CbGeoUtils.Geometry>, long, int, int); + ctor public SmsCbMessage(int, int, int, @NonNull android.telephony.SmsCbLocation, int, @Nullable String, int, @Nullable String, int, @Nullable android.telephony.SmsCbEtwsInfo, @Nullable android.telephony.SmsCbCmasInfo, int, @Nullable java.util.List<android.telephony.CbGeoUtils.Geometry>, long, int, int); method @NonNull public static android.telephony.SmsCbMessage createFromCursor(@NonNull android.database.Cursor); method public int describeContents(); method @Nullable public android.telephony.SmsCbCmasInfo getCmasWarningInfo(); method @NonNull public android.content.ContentValues getContentValues(); + method public int getDataCodingScheme(); method @Nullable public android.telephony.SmsCbEtwsInfo getEtwsWarningInfo(); method public int getGeographicalScope(); method @NonNull public java.util.List<android.telephony.CbGeoUtils.Geometry> getGeometries(); diff --git a/api/test-current.txt b/api/test-current.txt index fec5f3df9669..b3b74c896153 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2846,6 +2846,7 @@ package android.provider { field public static final String CMAS_SEVERITY = "cmas_severity"; field public static final String CMAS_URGENCY = "cmas_urgency"; field @NonNull public static final android.net.Uri CONTENT_URI; + field public static final String DATA_CODING_SCHEME = "dcs"; field public static final String DEFAULT_SORT_ORDER = "date DESC"; field public static final String DELIVERY_TIME = "date"; field public static final String ETWS_WARNING_TYPE = "etws_warning_type"; @@ -2853,9 +2854,11 @@ package android.provider { field public static final String GEOMETRIES = "geometries"; field public static final String LAC = "lac"; field public static final String LANGUAGE_CODE = "language"; + field public static final String LOCATION_CHECK_TIME = "location_check_time"; field public static final String MAXIMUM_WAIT_TIME = "maximum_wait_time"; field public static final String MESSAGE_BODY = "body"; field public static final String MESSAGE_BROADCASTED = "message_broadcasted"; + field public static final String MESSAGE_DISPLAYED = "message_displayed"; field public static final String MESSAGE_FORMAT = "format"; field @NonNull @RequiresPermission(android.Manifest.permission.READ_CELL_BROADCASTS) public static final android.net.Uri MESSAGE_HISTORY_URI; field public static final String MESSAGE_PRIORITY = "priority"; @@ -3475,6 +3478,7 @@ package android.telephony { method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setCellIdentity(@Nullable android.telephony.CellIdentity); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setDomain(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setEmergencyOnly(boolean); + method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegisteredPlmn(@Nullable String); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegistrationState(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRejectCause(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setTransportType(int); diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 2537edaf9e81..520366f518ab 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -17,6 +17,8 @@ #define LOG_NDEBUG 0 #define LOG_TAG "BootAnimation" +#include <vector> + #include <stdint.h> #include <inttypes.h> #include <sys/inotify.h> @@ -80,6 +82,10 @@ static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.z static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip"; static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip"; +static constexpr const char* PRODUCT_USERSPACE_REBOOT_ANIMATION_FILE = "/product/media/userspace-reboot.zip"; +static constexpr const char* OEM_USERSPACE_REBOOT_ANIMATION_FILE = "/oem/media/userspace-reboot.zip"; +static constexpr const char* SYSTEM_USERSPACE_REBOOT_ANIMATION_FILE = "/system/media/userspace-reboot.zip"; + static const char SYSTEM_DATA_DIR_PATH[] = "/data/system"; static const char SYSTEM_TIME_DIR_NAME[] = "time"; static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time"; @@ -384,6 +390,16 @@ bool BootAnimation::preloadAnimation() { return false; } +bool BootAnimation::findBootAnimationFileInternal(const std::vector<std::string> &files) { + for (const std::string& f : files) { + if (access(f.c_str(), R_OK) == 0) { + mZipFileName = f.c_str(); + return true; + } + } + return false; +} + void BootAnimation::findBootAnimationFile() { // If the device has encryption turned on or is in process // of being encrypted we show the encrypted boot animation. @@ -394,28 +410,33 @@ void BootAnimation::findBootAnimationFile() { !strcmp("trigger_restart_min_framework", decrypt); if (!mShuttingDown && encryptedAnimation) { - static const char* encryptedBootFiles[] = - {PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE}; - for (const char* f : encryptedBootFiles) { - if (access(f, R_OK) == 0) { - mZipFileName = f; - return; - } + static const std::vector<std::string> encryptedBootFiles = { + PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, + }; + if (findBootAnimationFileInternal(encryptedBootFiles)) { + return; } } const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1; - static const char* bootFiles[] = - {APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE, - OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; - static const char* shutdownFiles[] = - {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE, ""}; - - for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) { - if (access(f, R_OK) == 0) { - mZipFileName = f; - return; - } + static const std::vector<std::string> bootFiles = { + APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE, + OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE + }; + static const std::vector<std::string> shutdownFiles = { + PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE, "" + }; + static const std::vector<std::string> userspaceRebootFiles = { + PRODUCT_USERSPACE_REBOOT_ANIMATION_FILE, OEM_USERSPACE_REBOOT_ANIMATION_FILE, + SYSTEM_USERSPACE_REBOOT_ANIMATION_FILE, + }; + + if (android::base::GetBoolProperty("sys.init.userspace_reboot.in_progress", false)) { + findBootAnimationFileInternal(userspaceRebootFiles); + } else if (mShuttingDown) { + findBootAnimationFileInternal(shutdownFiles); + } else { + findBootAnimationFileInternal(bootFiles); } } diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index dc19fb09ef1d..574d65e425cf 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -17,6 +17,8 @@ #ifndef ANDROID_BOOTANIMATION_H #define ANDROID_BOOTANIMATION_H +#include <vector> + #include <stdint.h> #include <sys/types.h> @@ -157,6 +159,7 @@ private: bool parseAnimationDesc(Animation&); bool preloadZip(Animation &animation); void findBootAnimationFile(); + bool findBootAnimationFileInternal(const std::vector<std::string>& files); bool preloadAnimation(); void checkExit(); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 5ac00d05f022..1fdc8af70f67 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -78,9 +78,9 @@ message Atom { // Pushed atoms start at 2. oneof pushed { // For StatsLog reasons, 1 is illegal and will not work. Must start at 2. - BleScanStateChanged ble_scan_state_changed = 2; + BleScanStateChanged ble_scan_state_changed = 2 [(module) = "bluetooth"]; ProcessStateChanged process_state_changed = 3; - BleScanResultReceived ble_scan_result_received = 4; + BleScanResultReceived ble_scan_result_received = 4 [(module) = "bluetooth"]; SensorStateChanged sensor_state_changed = 5; GpsScanStateChanged gps_scan_state_changed = 6; SyncStateChanged sync_state_changed = 7; @@ -144,7 +144,8 @@ message Atom { AppDied app_died = 65; ResourceConfigurationChanged resource_configuration_changed = 66; BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67; - BluetoothConnectionStateChanged bluetooth_connection_state_changed = 68; + BluetoothConnectionStateChanged bluetooth_connection_state_changed = + 68 [(module) = "bluetooth"]; GpsSignalQualityChanged gps_signal_quality_changed = 69; UsbConnectorStateChanged usb_connector_state_changed = 70; SpeakerImpedanceReported speaker_impedance_reported = 71; @@ -207,9 +208,12 @@ message Atom { RescuePartyResetReported rescue_party_reset_reported = 122; SignedConfigReported signed_config_reported = 123; GnssNiEventReported gnss_ni_event_reported = 124; - BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = 125; - BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed = 126; - BluetoothScoConnectionStateChanged bluetooth_sco_connection_state_changed = 127; + BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = + 125 [(module) = "bluetooth"]; + BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed = + 126 [(module) = "bluetooth"]; + BluetoothScoConnectionStateChanged bluetooth_sco_connection_state_changed = + 127 [(module) = "bluetooth"]; AppDowngraded app_downgraded = 128; AppOptimizedAfterDowngraded app_optimized_after_downgraded = 129; LowStorageStateChanged low_storage_state_changed = 130; @@ -233,23 +237,40 @@ message Atom { BiometricSystemHealthIssueDetected biometric_system_health_issue_detected = 148; BubbleUIChanged bubble_ui_changed = 149 [(module) = "sysui"]; ScheduledJobConstraintChanged scheduled_job_constraint_changed = 150; - BluetoothActiveDeviceChanged bluetooth_active_device_changed = 151; - BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed = 152; - BluetoothA2dpCodecConfigChanged bluetooth_a2dp_codec_config_changed = 153; - BluetoothA2dpCodecCapabilityChanged bluetooth_a2dp_codec_capability_changed = 154; - BluetoothA2dpAudioUnderrunReported bluetooth_a2dp_audio_underrun_reported = 155; - BluetoothA2dpAudioOverrunReported bluetooth_a2dp_audio_overrun_reported = 156; - BluetoothDeviceRssiReported bluetooth_device_rssi_reported = 157; - BluetoothDeviceFailedContactCounterReported bluetooth_device_failed_contact_counter_reported = 158; - BluetoothDeviceTxPowerLevelReported bluetooth_device_tx_power_level_reported = 159; - BluetoothHciTimeoutReported bluetooth_hci_timeout_reported = 160; - BluetoothQualityReportReported bluetooth_quality_report_reported = 161; - BluetoothDeviceInfoReported bluetooth_device_info_reported = 162; - BluetoothRemoteVersionInfoReported bluetooth_remote_version_info_reported = 163; - BluetoothSdpAttributeReported bluetooth_sdp_attribute_reported = 164; - BluetoothBondStateChanged bluetooth_bond_state_changed = 165; - BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported = 166; - BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = 167; + BluetoothActiveDeviceChanged bluetooth_active_device_changed = + 151 [(module) = "bluetooth"]; + BluetoothA2dpPlaybackStateChanged bluetooth_a2dp_playback_state_changed = + 152 [(module) = "bluetooth"]; + BluetoothA2dpCodecConfigChanged bluetooth_a2dp_codec_config_changed = + 153 [(module) = "bluetooth"]; + BluetoothA2dpCodecCapabilityChanged bluetooth_a2dp_codec_capability_changed = + 154 [(module) = "bluetooth"]; + BluetoothA2dpAudioUnderrunReported bluetooth_a2dp_audio_underrun_reported = + 155 [(module) = "bluetooth"]; + BluetoothA2dpAudioOverrunReported bluetooth_a2dp_audio_overrun_reported = + 156 [(module) = "bluetooth"]; + BluetoothDeviceRssiReported bluetooth_device_rssi_reported = + 157 [(module) = "bluetooth"]; + BluetoothDeviceFailedContactCounterReported + bluetooth_device_failed_contact_counter_reported = 158 [(module) = "bluetooth"]; + BluetoothDeviceTxPowerLevelReported bluetooth_device_tx_power_level_reported = + 159 [(module) = "bluetooth"]; + BluetoothHciTimeoutReported bluetooth_hci_timeout_reported = + 160 [(module) = "bluetooth"]; + BluetoothQualityReportReported bluetooth_quality_report_reported = + 161 [(module) = "bluetooth"]; + BluetoothDeviceInfoReported bluetooth_device_info_reported = + 162 [(module) = "bluetooth"]; + BluetoothRemoteVersionInfoReported bluetooth_remote_version_info_reported = + 163 [(module) = "bluetooth"]; + BluetoothSdpAttributeReported bluetooth_sdp_attribute_reported = + 164 [(module) = "bluetooth"]; + BluetoothBondStateChanged bluetooth_bond_state_changed = + 165 [(module) = "bluetooth"]; + BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported = + 166 [(module) = "bluetooth"]; + BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = + 167 [(module) = "bluetooth"]; ScreenTimeoutExtensionReported screen_timeout_extension_reported = 168; ProcessStartTime process_start_time = 169; PermissionGrantRequestResultReported permission_grant_request_result_reported = @@ -258,9 +279,9 @@ message Atom { DeviceIdentifierAccessDenied device_identifier_access_denied = 172 [(module) = "telephony_common"]; BubbleDeveloperErrorReported bubble_developer_error_reported = 173; - AssistGestureStageReported assist_gesture_stage_reported = 174; - AssistGestureFeedbackReported assist_gesture_feedback_reported = 175; - AssistGestureProgressReported assist_gesture_progress_reported = 176; + AssistGestureStageReported assist_gesture_stage_reported = 174 [(module) = "sysui"]; + AssistGestureFeedbackReported assist_gesture_feedback_reported = 175 [(module) = "sysui"]; + AssistGestureProgressReported assist_gesture_progress_reported = 176 [(module) = "sysui"]; TouchGestureClassified touch_gesture_classified = 177; HiddenApiUsed hidden_api_used = 178 [(allow_from_any_uid) = true]; StyleUIChanged style_ui_changed = 179 [(module) = "sysui"]; @@ -272,7 +293,8 @@ message Atom { BiometricEnrolled biometric_enrolled = 184; SystemServerWatchdogOccurred system_server_watchdog_occurred = 185; TombStoneOccurred tomb_stone_occurred = 186; - BluetoothClassOfDeviceReported bluetooth_class_of_device_reported = 187; + BluetoothClassOfDeviceReported bluetooth_class_of_device_reported = + 187 [(module) = "bluetooth"]; IntelligenceEventReported intelligence_event_reported = 188 [(module) = "intelligence"]; ThermalThrottlingSeverityStateChanged thermal_throttling_severity_state_changed = 189; diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java index d82b151e9ce9..47fc7e1cdf13 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -17,6 +17,12 @@ package android.accessibilityservice; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_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; @@ -60,6 +66,12 @@ public final class AccessibilityGestureEvent implements Parcelable { /** @hide */ @IntDef(prefix = { "GESTURE_" }, value = { + GESTURE_2_FINGER_SINGLE_TAP, + GESTURE_2_FINGER_DOUBLE_TAP, + GESTURE_2_FINGER_TRIPLE_TAP, + GESTURE_3_FINGER_SINGLE_TAP, + GESTURE_3_FINGER_DOUBLE_TAP, + GESTURE_3_FINGER_TRIPLE_TAP, GESTURE_DOUBLE_TAP, GESTURE_DOUBLE_TAP_AND_HOLD, GESTURE_SWIPE_UP, @@ -122,13 +134,43 @@ public final class AccessibilityGestureEvent implements Parcelable { @Override public String toString() { StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureEvent["); - stringBuilder.append("gestureId: ").append(mGestureId); + stringBuilder.append("gestureId: ").append(eventTypeToString(mGestureId)); stringBuilder.append(", "); stringBuilder.append("displayId: ").append(mDisplayId); stringBuilder.append(']'); return stringBuilder.toString(); } + private static String eventTypeToString(int eventType) { + switch (eventType) { + case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP"; + case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP"; + case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP"; + 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_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"; + case GESTURE_SWIPE_DOWN_AND_LEFT: return "GESTURE_SWIPE_DOWN_AND_LEFT"; + case GESTURE_SWIPE_DOWN_AND_UP: return "GESTURE_SWIPE_DOWN_AND_UP"; + case GESTURE_SWIPE_DOWN_AND_RIGHT: return "GESTURE_SWIPE_DOWN_AND_RIGHT"; + case GESTURE_SWIPE_LEFT: return "GESTURE_SWIPE_LEFT"; + case GESTURE_SWIPE_LEFT_AND_UP: return "GESTURE_SWIPE_LEFT_AND_UP"; + case GESTURE_SWIPE_LEFT_AND_RIGHT: return "GESTURE_SWIPE_LEFT_AND_RIGHT"; + case GESTURE_SWIPE_LEFT_AND_DOWN: return "GESTURE_SWIPE_LEFT_AND_DOWN"; + case GESTURE_SWIPE_RIGHT: return "GESTURE_SWIPE_RIGHT"; + case GESTURE_SWIPE_RIGHT_AND_UP: return "GESTURE_SWIPE_RIGHT_AND_UP"; + case GESTURE_SWIPE_RIGHT_AND_LEFT: return "GESTURE_SWIPE_RIGHT_AND_LEFT"; + case GESTURE_SWIPE_RIGHT_AND_DOWN: return "GESTURE_SWIPE_RIGHT_AND_DOWN"; + case GESTURE_SWIPE_UP: return "GESTURE_SWIPE_UP"; + case GESTURE_SWIPE_UP_AND_LEFT: return "GESTURE_SWIPE_UP_AND_LEFT"; + case GESTURE_SWIPE_UP_AND_DOWN: return "GESTURE_SWIPE_UP_AND_DOWN"; + case GESTURE_SWIPE_UP_AND_RIGHT: return "GESTURE_SWIPE_UP_AND_RIGHT"; + default: return Integer.toHexString(eventType); + } + } + /** * {@inheritDoc} */ diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 7722dc318fd7..27cd2857f38f 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -319,6 +319,36 @@ public abstract class AccessibilityService extends Service { public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18; /** + * The user has performed a two-finger single tap gesture on the touch screen. + */ + public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; + + /** + * The user has performed a two-finger double tap gesture on the touch screen. + */ + public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; + + /** + * The user has performed a two-finger triple tap gesture on the touch screen. + */ + public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; + + /** + * The user has performed a three-finger single tap gesture on the touch screen. + */ + public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; + + /** + * The user has performed a three-finger double tap gesture on the touch screen. + */ + public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; + + /** + * The user has performed a three-finger triple tap gesture on the touch screen. + */ + public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24; + + /** * The {@link Intent} that must be declared as handled by the service. */ public static final String SERVICE_INTERFACE = diff --git a/core/java/android/annotation/SystemApi.java b/core/java/android/annotation/SystemApi.java index 1a8b78ff08cf..ecbfed96cefd 100644 --- a/core/java/android/annotation/SystemApi.java +++ b/core/java/android/annotation/SystemApi.java @@ -23,6 +23,7 @@ import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PACKAGE; import static java.lang.annotation.ElementType.TYPE; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -40,6 +41,7 @@ import java.lang.annotation.Target; */ @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE}) @Retention(RetentionPolicy.RUNTIME) +@Repeatable(SystemApi.Container.class) // TODO(b/146727827): make this non-repeatable public @interface SystemApi { enum Client { /** @@ -95,4 +97,14 @@ public @interface SystemApi { */ @Deprecated Process process() default android.annotation.SystemApi.Process.ALL; + + + /** + * Container for {@link SystemApi} that allows it to be applied repeatedly to types. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(TYPE) + @interface Container { + SystemApi[] value(); + } } diff --git a/core/java/android/annotation/UnsupportedAppUsage.java b/core/java/android/annotation/UnsupportedAppUsage.java deleted file mode 100644 index 1af48cb63079..000000000000 --- a/core/java/android/annotation/UnsupportedAppUsage.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.annotation; - -import static java.lang.annotation.ElementType.CONSTRUCTOR; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.CLASS; - -import java.lang.annotation.Repeatable; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * Indicates that this non-SDK interface is used by apps. A non-SDK interface is a - * class member (field or method) that is not part of the public SDK. Since the - * member is not part of the SDK, usage by apps is not supported. - * - * <h2>If you are an Android App developer</h2> - * - * This annotation indicates that you may be able to access the member, but that - * this access is discouraged and not supported by Android. If there is a value - * for {@link #maxTargetSdk()} on the annotation, access will be restricted based - * on the {@code targetSdkVersion} value set in your manifest. - * - * <p>Fields and methods annotated with this are likely to be restricted, changed - * or removed in future Android releases. If you rely on these members for - * functionality that is not otherwise supported by Android, consider filing a - * <a href="http://g.co/dev/appcompat">feature request</a>. - * - * <h2>If you are an Android OS developer</h2> - * - * This annotation acts as a heads up that changing a given method or field - * may affect apps, potentially breaking them when the next Android version is - * released. In some cases, for members that are heavily used, this annotation - * may imply restrictions on changes to the member. - * - * <p>This annotation also results in access to the member being permitted by the - * runtime, with a warning being generated in debug builds. Which apps can access - * the member is determined by the value of {@link #maxTargetSdk()}. - * - * <p>For more details, see go/UnsupportedAppUsage. - * - * {@hide} - */ -@Retention(CLASS) -@Target({CONSTRUCTOR, METHOD, FIELD, TYPE}) -@Repeatable(UnsupportedAppUsage.Container.class) -public @interface UnsupportedAppUsage { - - /** - * Associates a bug tracking the work to add a public alternative to this API. Optional. - * - * @return ID of the associated tracking bug - */ - long trackingBug() default 0; - - /** - * Indicates that usage of this API is limited to apps based on their target SDK version. - * - * <p>Access to the API is allowed if the targetSdkVersion in the apps manifest is no greater - * than this value. Access checks are performed at runtime. - * - * <p>This is used to give app developers a grace period to migrate off a non-SDK interface. - * When making Android version N, existing APIs can have a maxTargetSdk of N-1 added to them. - * Developers must then migrate off the API when their app is updated in future, but it will - * continue working in the meantime. - * - * <p>Possible values are: - * <ul> - * <li> - * An API level like {@link android.os.Build.VERSION_CODES#O} - in which case the API is - * available up to and including the specified release. Or, in other words, the API is - * blacklisted (unavailable) from the next API level from the one specified. - * </li> - * <li> - * absent (default value) - All apps can access this API, but doing so may result in - * warnings in the log, UI warnings (on developer builds) and/or strictmode violations. - * The API is likely to be further restricted in future. - * </li> - * - * </ul> - * - * @return The maximum value for an apps targetSdkVersion in order to access this API. - */ - int maxTargetSdk() default Integer.MAX_VALUE; - - /** - * For debug use only. The expected dex signature to be generated for this API, used to verify - * parts of the build process. - * - * @return A dex API signature. - */ - String expectedSignature() default ""; - - /** - * The signature of an implicit (not present in the source) member that forms part of the - * hiddenapi. - * - * <p>Allows access to non-SDK API elements that are not represented in the input source to be - * managed. - * - * <p>This must only be used when applying the annotation to a type, using it in any other - * situation is an error. - * - * @return A dex API signature. - */ - String implicitMember() default ""; - - /** - * Public API alternatives to this API. - * - * <p>If non-empty, the string must be a description of the public API alternative(s) to this - * API. The explanation must contain at least one Javadoc link tag to public API methods or - * fields. e.g.: - * {@literal @UnsupportedAppUsage(publicAlternatives="Use {@link foo.bar.Baz#bat()} instead.")} - * - * <p>Any elements that can be deduced can be omitted, e.g.: - * <ul> - * <li> - * the class, if it's the same as for the annotated element. - * </li> - * <li> - * the package name, if it's the same as for the annotated element. - * </li> - * <li> - * the method parameters, if there is only one method with that name in the given - * package and class. - * </li> - * </ul> - * @return A Javadoc-formatted string. - */ - @SuppressWarnings("JavadocReference") - String publicAlternatives() default ""; - - /** - * Override the default source position when generating an index of the annotations. - * - * <p>This is intended for use by tools that generate java source code, to point to the - * original source position of the annotation, rather than the position within the generated - * code. It should never be set manually. - * - * <p>The format of the value is "path/to/file:startline:startcol:endline:endcol" indicating - * the position of the annotation itself. - */ - String overrideSourcePosition() default ""; - - /** - * Container for {@link UnsupportedAppUsage} that allows it to be applied repeatedly to types. - */ - @Retention(CLASS) - @Target(TYPE) - @interface Container { - UnsupportedAppUsage[] value(); - } -} diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d90e81f09800..c901d2a29821 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2198,7 +2198,7 @@ public final class ActivityThread extends ClientTransactionHandler { Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, LoadedApk pkgInfo) { return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs, - displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader()); + displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), null); } @UnsupportedAppUsage diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index c03413c80aa0..00627ef381ab 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -35,7 +35,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.proto.ProtoOutputStream; -import libcore.timezone.ZoneInfoDB; +import libcore.timezone.ZoneInfoDb; import java.io.IOException; import java.lang.annotation.Retention; @@ -1000,7 +1000,7 @@ public class AlarmManager { if (mTargetSdkVersion >= Build.VERSION_CODES.M) { boolean hasTimeZone = false; try { - hasTimeZone = ZoneInfoDB.getInstance().hasTimeZone(timeZone); + hasTimeZone = ZoneInfoDb.getInstance().hasTimeZone(timeZone); } catch (IOException ignored) { } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index b7555ee1c04e..136c84eaf543 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -46,6 +46,7 @@ import android.content.res.CompatResources; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.loader.ResourcesLoader; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; @@ -100,6 +101,7 @@ import java.lang.annotation.RetentionPolicy; import java.nio.ByteOrder; import java.nio.file.Path; import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -2217,7 +2219,8 @@ class ContextImpl extends Context { } private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName, - int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { + int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo, + List<ResourcesLoader> resourcesLoader) { final String[] splitResDirs; final ClassLoader classLoader; try { @@ -2234,7 +2237,8 @@ class ContextImpl extends Context { displayId, overrideConfig, compatInfo, - classLoader); + classLoader, + resourcesLoader); } @Override @@ -2249,7 +2253,7 @@ class ContextImpl extends Context { final int displayId = getDisplayId(); c.setResources(createResources(mToken, pi, null, displayId, null, - getDisplayAdjustments(displayId).getCompatibilityInfo())); + getDisplayAdjustments(displayId).getCompatibilityInfo(), null)); if (c.mResources != null) { return c; } @@ -2284,7 +2288,7 @@ class ContextImpl extends Context { final int displayId = getDisplayId(); c.setResources(createResources(mToken, pi, null, displayId, null, - getDisplayAdjustments(displayId).getCompatibilityInfo())); + getDisplayAdjustments(displayId).getCompatibilityInfo(), null)); if (c.mResources != null) { return c; } @@ -2328,7 +2332,8 @@ class ContextImpl extends Context { displayId, null, mPackageInfo.getCompatibilityInfo(), - classLoader)); + classLoader, + mResources.getLoaders())); return context; } @@ -2342,8 +2347,10 @@ class ContextImpl extends Context { mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = getDisplayId(); + context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, - overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); + overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(), + mResources.getLoaders())); return context; } @@ -2357,8 +2364,10 @@ class ContextImpl extends Context { mSplitName, mToken, mUser, mFlags, mClassLoader, null); final int displayId = display.getDisplayId(); + context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, - null, getDisplayAdjustments(displayId).getCompatibilityInfo())); + null, getDisplayAdjustments(displayId).getCompatibilityInfo(), + mResources.getLoaders())); context.mDisplay = display; return context; } @@ -2564,7 +2573,7 @@ class ContextImpl extends Context { ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null, null, null, null, 0, null, null); context.setResources(createResources(null, packageInfo, null, displayId, null, - packageInfo.getCompatibilityInfo())); + packageInfo.getCompatibilityInfo(), null)); context.updateDisplay(displayId); context.mIsSystemOrSystemUiContext = true; return context; @@ -2637,7 +2646,8 @@ class ContextImpl extends Context { displayId, overrideConfiguration, compatInfo, - classLoader)); + classLoader, + packageInfo.getApplication().getResources().getLoaders())); context.mDisplay = resourcesManager.getAdjustedDisplay(displayId, context.getResources()); return context; diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 3ffd7c70b40d..5f3bad6ad1a8 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -303,8 +303,13 @@ interface IActivityManager { boolean isTopActivityImmersive(); void crashApplication(int uid, int initialPid, in String packageName, int userId, in String message, boolean force); - @UnsupportedAppUsage + /** @deprecated -- use getProviderMimeTypeAsync */ + @UnsupportedAppUsage(maxTargetSdk = 29, publicAlternatives = + "Use {@link android.content.ContentResolver#getType} public API instead.") String getProviderMimeType(in Uri uri, int userId); + + oneway void getProviderMimeTypeAsync(in Uri uri, int userId, in RemoteCallback resultCallback); + // Cause the specified process to dump the specified heap. boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo, boolean runGc, in String path, in ParcelFileDescriptor fd, diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index f0d0e98b841f..44c248637f49 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -365,7 +365,8 @@ public final class LoadedApk { mResources = ResourcesManager.getInstance().getResources(null, mResDir, splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), - getClassLoader()); + getClassLoader(), mApplication == null ? null + : mApplication.getResources().getLoaders()); } } mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader); @@ -1158,7 +1159,7 @@ public final class LoadedApk { mResources = ResourcesManager.getInstance().getResources(null, mResDir, splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), - getClassLoader()); + getClassLoader(), null); } return mResources; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index b346e8f20feb..35d26aba9094 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3569,8 +3569,16 @@ public class Notification implements Parcelable * This field will be ignored by Launchers that don't support badging, don't show * notification content, or don't show {@link android.content.pm.ShortcutManager shortcuts}. * + * If this notification has {@link BubbleMetadata} attached that was created with + * {@link BubbleMetadata.Builder#createShortcutBubble(String)} a check will be performed + * to ensure the shortcutId supplied to bubble metadata matches the shortcutId set here, + * if one was set. If the shortcutId's were specified but do not match, an exception + * is thrown. + * * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification * supersedes + * + * @see BubbleMetadata.Builder#createShortcutBubble(String) */ @NonNull public Builder setShortcutId(String shortcutId) { @@ -5926,9 +5934,29 @@ public class Notification implements Parcelable /** * Combine all of the options that have been set and return a new {@link Notification} * object. + * + * If this notification has {@link BubbleMetadata} attached that was created with + * {@link BubbleMetadata.Builder#createShortcutBubble(String)} a check will be performed + * to ensure the shortcutId supplied to bubble metadata matches the shortcutId set on the + * notification builder, if one was set. If the shortcutId's were specified but do not + * match, an exception is thrown here. + * + * @see BubbleMetadata.Builder#createShortcutBubble(String) + * @see #setShortcutId(String) */ @NonNull public Notification build() { + // Check shortcut id matches + if (mN.mShortcutId != null + && mN.mBubbleMetadata != null + && mN.mBubbleMetadata.getShortcutId() != null + && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) { + throw new IllegalArgumentException( + "Notification and BubbleMetadata shortcut id's don't match," + + " notification: " + mN.mShortcutId + + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); + } + // first, add any extras from the calling code if (mUserExtras != null) { mN.extras = getAllExtras(); @@ -8644,17 +8672,23 @@ public class Notification implements Parcelable public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; /** - * If set and the app posting the bubble is in the foreground, the bubble will - * be posted <b>without</b> the associated notification in the notification shade. + * Indicates whether the notification associated with the bubble is being visually + * suppressed from the notification shade. When <code>true</code> the notification is + * hidden, when <code>false</code> the notification shows as normal. * - * <p>This flag has no effect if the app posting the bubble is not in the foreground. - * The app is considered foreground if it is visible and on the screen, note that - * a foreground service does not qualify. - * </p> + * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> + * the associated notification in the notification shade.</p> * - * <p>Generally this flag should only be set if the user has performed an action to request - * or create a bubble, or if the user has seen the content in the notification and the - * notification is no longer relevant.</p> + * <p>Apps sending bubbles can only apply this flag when the app is in the foreground, + * otherwise the flag is not respected. The app is considered foreground if it is visible + * and on the screen, note that a foreground service does not qualify.</p> + * + * <p>Generally this flag should only be set by the app if the user has performed an + * action to request or create a bubble, or if the user has seen the content in the + * notification and the notification is no longer relevant. </p> + * + * <p>The system will also update this flag with <code>true</code> to hide the notification + * from the user once the bubble has been expanded. </p> * * @hide */ @@ -8772,6 +8806,24 @@ public class Notification implements Parcelable } /** + * Indicates whether the notification associated with the bubble is being visually + * suppressed from the notification shade. When <code>true</code> the notification is + * hidden, when <code>false</code> the notification shows as normal. + * + * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> + * the associated notification in the notification shade.</p> + * + * <p>Apps sending bubbles can only apply this flag when the app is in the foreground, + * otherwise the flag is not respected. The app is considered foreground if it is visible + * and on the screen, note that a foreground service does not qualify.</p> + * + * <p>Generally the app should only set this flag if the user has performed an + * action to request or create a bubble, or if the user has seen the content in the + * notification and the notification is no longer relevant. </p> + * + * <p>The system will update this flag with <code>true</code> to hide the notification + * from the user once the bubble has been expanded.</p> + * * @return whether this bubble should suppress the notification when it is posted. * * @see BubbleMetadata.Builder#setSuppressNotification(boolean) diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 7ab85a4a7468..d09f0bcf4275 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -32,7 +32,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.ResourcesImpl; import android.content.res.ResourcesKey; -import android.content.res.loader.ResourceLoader; +import android.content.res.loader.ResourcesLoader; import android.hardware.display.DisplayManagerGlobal; import android.os.IBinder; import android.os.Process; @@ -46,7 +46,6 @@ import android.util.Slog; import android.view.Display; import android.view.DisplayAdjustments; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; @@ -57,9 +56,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Predicate; @@ -98,52 +95,6 @@ public class ResourcesManager { new ArrayMap<>(); /** - * A list of {@link Resources} that contain unique {@link ResourcesImpl}s. - * - * These are isolated so that {@link ResourceLoader}s can be added and removed without - * affecting other instances. - * - * When a reference is added here, it is guaranteed that the {@link ResourcesImpl} - * it contains is unique to itself and will never be set to a shared reference. - */ - @GuardedBy("this") - private List<ResourcesWithLoaders> mResourcesWithLoaders = Collections.emptyList(); - - private static class ResourcesWithLoaders { - - private WeakReference<Resources> mResources; - private ResourcesKey mResourcesKey; - - @Nullable - private WeakReference<IBinder> mActivityToken; - - ResourcesWithLoaders(Resources resources, ResourcesKey resourcesKey, - IBinder activityToken) { - this.mResources = new WeakReference<>(resources); - this.mResourcesKey = resourcesKey; - this.mActivityToken = new WeakReference<>(activityToken); - } - - @Nullable - Resources resources() { - return mResources.get(); - } - - @Nullable - IBinder activityToken() { - return mActivityToken == null ? null : mActivityToken.get(); - } - - ResourcesKey resourcesKey() { - return mResourcesKey; - } - - void updateKey(ResourcesKey newKey) { - mResourcesKey = newKey; - } - } - - /** * A list of Resource references that can be reused. */ @UnsupportedAppUsage @@ -219,6 +170,11 @@ public class ResourcesManager { private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mAdjustedDisplays = new ArrayMap<>(); + /** + * Callback implementation for handling updates to Resources objects. + */ + private final UpdateHandler mUpdateCallbacks = new UpdateHandler(); + @UnsupportedAppUsage public ResourcesManager() { } @@ -253,24 +209,6 @@ public class ResourcesManager { } } - for (int i = mResourcesWithLoaders.size() - 1; i >= 0; i--) { - ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(i); - Resources resources = resourcesWithLoaders.resources(); - if (resources == null) { - continue; - } - - final ResourcesKey key = resourcesWithLoaders.resourcesKey(); - if (key.isPathReferenced(path)) { - mResourcesWithLoaders.remove(i); - ResourcesImpl impl = resources.getImpl(); - if (impl != null) { - impl.flushLayoutCache(); - } - count++; - } - } - Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path); for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) { @@ -513,6 +451,12 @@ public class ResourcesManager { } } + if (key.mLoaders != null) { + for (final ResourcesLoader loader : key.mLoaders) { + builder.addLoader(loader); + } + } + return builder.build(); } @@ -570,16 +514,6 @@ public class ResourcesManager { pw.print("resource impls: "); pw.println(countLiveReferences(mResourceImpls.values())); - - int resourcesWithLoadersCount = 0; - for (int index = 0; index < mResourcesWithLoaders.size(); index++) { - if (mResourcesWithLoaders.get(index).resources() != null) { - resourcesWithLoadersCount++; - } - } - - pw.print("resources with loaders: "); - pw.println(resourcesWithLoadersCount); } } @@ -660,19 +594,6 @@ public class ResourcesManager { */ private @Nullable ResourcesKey findKeyForResourceImplLocked( @NonNull ResourcesImpl resourceImpl) { - int size = mResourcesWithLoaders.size(); - for (int index = 0; index < size; index++) { - ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index); - Resources resources = resourcesWithLoaders.resources(); - if (resources == null) { - continue; - } - - if (resourceImpl == resources.getImpl()) { - return resourcesWithLoaders.resourcesKey(); - } - } - int refCount = mResourceImpls.size(); for (int i = 0; i < refCount; i++) { WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); @@ -722,30 +643,10 @@ public class ResourcesManager { @Nullable private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken, @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) { - int size = mResourcesWithLoaders.size(); - for (int index = 0; index < size; index++) { - ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index); - Resources resources = resourcesWithLoaders.resources(); - if (resources == null) { - continue; - } - - IBinder activityToken = resourcesWithLoaders.activityToken(); - ResourcesKey key = resourcesWithLoaders.resourcesKey(); - - ClassLoader classLoader = resources.getClassLoader(); - - if (Objects.equals(activityToken, targetActivityToken) - && Objects.equals(key, targetKey) - && Objects.equals(classLoader, targetClassLoader)) { - return resources; - } - } - ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( targetActivityToken); - size = activityResources.activityResources.size(); + final int size = activityResources.activityResources.size(); for (int index = 0; index < size; index++) { WeakReference<Resources> ref = activityResources.activityResources.get(index); Resources resources = ref.get(); @@ -771,6 +672,7 @@ public class ResourcesManager { Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) : new Resources(classLoader); resources.setImpl(impl); + resources.setCallbacks(mUpdateCallbacks); activityResources.activityResources.add(new WeakReference<>(resources)); if (DEBUG) { Slog.d(TAG, "- creating new ref=" + resources); @@ -784,6 +686,7 @@ public class ResourcesManager { Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) : new Resources(classLoader); resources.setImpl(impl); + resources.setCallbacks(mUpdateCallbacks); mResourceReferences.add(new WeakReference<>(resources)); if (DEBUG) { Slog.d(TAG, "- creating new ref=" + resources); @@ -795,7 +698,7 @@ public class ResourcesManager { /** * Creates base resources for an Activity. Calls to * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, - * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override + * CompatibilityInfo, ClassLoader, List)} with the same activityToken will have their override * configurations merged with the one specified here. * * @param activityToken Represents an Activity. @@ -820,7 +723,8 @@ public class ResourcesManager { int displayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, - @Nullable ClassLoader classLoader) { + @Nullable ClassLoader classLoader, + @Nullable List<ResourcesLoader> loaders) { try { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#createBaseActivityResources"); @@ -831,7 +735,8 @@ public class ResourcesManager { libDirs, displayId, overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy - compatInfo); + compatInfo, + loaders == null ? null : loaders.toArray(new ResourcesLoader[0])); classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); if (DEBUG) { @@ -902,14 +807,6 @@ public class ResourcesManager { } else { ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate); } - - for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) { - ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index); - Resources resources = resourcesWithLoaders.resources(); - if (resources == null) { - mResourcesWithLoaders.remove(index); - } - } } } @@ -983,7 +880,8 @@ public class ResourcesManager { int displayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, - @Nullable ClassLoader classLoader) { + @Nullable ClassLoader classLoader, + @Nullable List<ResourcesLoader> loaders) { try { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources"); final ResourcesKey key = new ResourcesKey( @@ -993,7 +891,8 @@ public class ResourcesManager { libDirs, displayId, overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy - compatInfo); + compatInfo, + loaders == null ? null : loaders.toArray(new ResourcesLoader[0])); classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); cleanupReferences(activityToken); @@ -1010,10 +909,10 @@ public class ResourcesManager { /** * Updates an Activity's Resources object with overrideConfig. The Resources object - * that was previously returned by - * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, - * CompatibilityInfo, ClassLoader)} is - * still valid and will have the updated configuration. + * that was previously returned by {@link #getResources(IBinder, String, String[], String[], + * String[], int, Configuration, CompatibilityInfo, ClassLoader, List)} is still valid and will + * have the updated configuration. + * * @param activityToken The Activity token. * @param overrideConfig The configuration override to update. * @param displayId Id of the display where activity currently resides. @@ -1074,25 +973,6 @@ public class ResourcesManager { overrideConfig, displayId); updateActivityResources(resources, newKey, false); } - - // Also find loaders that are associated with an Activity - final int loaderCount = mResourcesWithLoaders.size(); - for (int index = loaderCount - 1; index >= 0; index--) { - ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get( - index); - Resources resources = resourcesWithLoaders.resources(); - if (resources == null - || resourcesWithLoaders.activityToken() != activityToken) { - continue; - } - - ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig, - overrideConfig, displayId); - - updateActivityResources(resources, newKey, true); - - resourcesWithLoaders.updateKey(newKey); - } } } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); @@ -1133,9 +1013,8 @@ public class ResourcesManager { // Create the new ResourcesKey with the rebased override config. final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir, - oldKey.mSplitResDirs, - oldKey.mOverlayDirs, oldKey.mLibDirs, displayId, - rebasedOverrideConfig, oldKey.mCompatInfo); + oldKey.mSplitResDirs, oldKey.mOverlayDirs, oldKey.mLibDirs, + displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders); if (DEBUG) { Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey @@ -1214,18 +1093,6 @@ public class ResourcesManager { } } - for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) { - ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index); - Resources resources = resourcesWithLoaders.resources(); - if (resources == null) { - mResourcesWithLoaders.remove(index); - continue; - } - - applyConfigurationToResourcesLocked(config, compat, tmpConfig, - resourcesWithLoaders.resourcesKey(), resources.getImpl()); - } - return changes != 0; } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); @@ -1306,37 +1173,8 @@ public class ResourcesManager { newLibAssets, key.mDisplayId, key.mOverrideConfiguration, - key.mCompatInfo)); - } - } - } - - final int count = mResourcesWithLoaders.size(); - for (int index = 0; index < count; index++) { - ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index); - Resources resources = resourcesWithLoaders.resources(); - if (resources == null) { - continue; - } - - ResourcesKey key = resourcesWithLoaders.resourcesKey(); - if (Objects.equals(key.mResDir, assetPath)) { - String[] newLibAssets = key.mLibDirs; - for (String libAsset : libAssets) { - newLibAssets = - ArrayUtils.appendElement(String.class, newLibAssets, libAsset); - } - - if (!Arrays.equals(newLibAssets, key.mLibDirs)) { - updatedResourceKeys.put(resources.getImpl(), - new ResourcesKey( - key.mResDir, - key.mSplitResDirs, - key.mOverlayDirs, - newLibAssets, - key.mDisplayId, - key.mOverrideConfiguration, - key.mCompatInfo)); + key.mCompatInfo, + key.mLoaders)); } } } @@ -1345,72 +1183,6 @@ public class ResourcesManager { } } - /** - * Mark a {@link Resources} as containing a {@link ResourceLoader}. - * - * This removes its {@link ResourcesImpl} from the shared pool and creates it a new one. It is - * then tracked separately, kept unique, and restored properly across {@link Activity} - * recreations. - */ - public void registerForLoaders(Resources resources) { - synchronized (this) { - boolean found = false; - IBinder activityToken = null; - - // Remove the Resources from the reference list as it's now tracked separately - int size = mResourceReferences.size(); - for (int index = 0; index < size; index++) { - WeakReference<Resources> reference = mResourceReferences.get(index); - if (reference.get() == resources) { - mResourceReferences.remove(index); - found = true; - break; - } - } - - if (!found) { - // Do the same removal for any Activity Resources - for (Map.Entry<IBinder, ActivityResources> entry : - mActivityResourceReferences.entrySet()) { - ArrayList<WeakReference<Resources>> activityResourcesList = - entry.getValue().activityResources; - final int resCount = activityResourcesList.size(); - for (int index = 0; index < resCount; index++) { - WeakReference<Resources> reference = activityResourcesList.get(index); - if (reference.get() == resources) { - activityToken = entry.getKey(); - activityResourcesList.remove(index); - found = true; - break; - } - } - - if (found) { - break; - } - } - } - - if (!found) { - throw new IllegalArgumentException("Resources " + resources - + " registered for loaders but was not previously tracked by" - + " ResourcesManager"); - } - - ResourcesKey key = findKeyForResourceImplLocked(resources.getImpl()); - ResourcesImpl impl = createResourcesImpl(key); - - if (mResourcesWithLoaders == Collections.EMPTY_LIST) { - mResourcesWithLoaders = Collections.synchronizedList(new ArrayList<>()); - } - - mResourcesWithLoaders.add(new ResourcesWithLoaders(resources, key, activityToken)); - - // Set the new Impl, which is now guaranteed to be unique per Resources object - resources.setImpl(impl); - } - } - // TODO(adamlesinski): Make this accept more than just overlay directories. final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo, @Nullable final String[] oldPaths) { @@ -1450,37 +1222,12 @@ public class ResourcesManager { key.mLibDirs, key.mDisplayId, key.mOverrideConfiguration, - key.mCompatInfo + key.mCompatInfo, + key.mLoaders )); } } - final int count = mResourcesWithLoaders.size(); - for (int index = 0; index < count; index++) { - ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index); - Resources resources = resourcesWithLoaders.resources(); - if (resources == null) { - continue; - } - - ResourcesKey key = resourcesWithLoaders.resourcesKey(); - - if (key.mResDir == null - || key.mResDir.equals(baseCodePath) - || ArrayUtils.contains(oldPaths, key.mResDir)) { - updatedResourceKeys.put(resources.getImpl(), - new ResourcesKey( - baseCodePath, - copiedSplitDirs, - copiedResourceDirs, - key.mLibDirs, - key.mDisplayId, - key.mOverrideConfiguration, - key.mCompatInfo - )); - } - } - redirectResourcesToNewImplLocked(updatedResourceKeys); } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); @@ -1530,25 +1277,62 @@ public class ResourcesManager { } } } + } - // Update any references that need to be re-built with loaders. These are intentionally not - // part of either of the above lists. - final int loaderCount = mResourcesWithLoaders.size(); - for (int index = loaderCount - 1; index >= 0; index--) { - ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index); - Resources resources = resourcesWithLoaders.resources(); - if (resources == null) { - continue; - } + private class UpdateHandler implements Resources.UpdateCallbacks { + + /** + * Updates the list of {@link ResourcesLoader ResourcesLoader(s)} that the {@code resources} + * instance uses. + */ + @Override + public void onLoadersChanged(Resources resources, List<ResourcesLoader> newLoader) { + synchronized (ResourcesManager.this) { + final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl()); + if (oldKey == null) { + throw new IllegalArgumentException("Cannot modify resource loaders of" + + " ResourcesImpl not registered with ResourcesManager"); + } - ResourcesKey newKey = updatedResourceKeys.get(resources.getImpl()); - if (newKey == null) { - continue; + final ResourcesKey newKey = new ResourcesKey( + oldKey.mResDir, + oldKey.mSplitResDirs, + oldKey.mOverlayDirs, + oldKey.mLibDirs, + oldKey.mDisplayId, + oldKey.mOverrideConfiguration, + oldKey.mCompatInfo, + newLoader.toArray(new ResourcesLoader[0])); + + final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(newKey); + resources.setImpl(impl); } + } + + /** + * Refreshes the {@link AssetManager} of all {@link ResourcesImpl} that contain the + * {@code loader} to apply any changes of the set of {@link ApkAssets}. + **/ + @Override + public void onLoaderUpdated(ResourcesLoader loader) { + synchronized (ResourcesManager.this) { + final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys = + new ArrayMap<>(); + + for (int i = mResourceImpls.size() - 1; i >= 0; i--) { + final ResourcesKey key = mResourceImpls.keyAt(i); + final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i); + if (impl == null || impl.get() == null + || !ArrayUtils.contains(key.mLoaders, loader)) { + continue; + } + + mResourceImpls.remove(key); + updatedResourceImplKeys.put(impl.get(), key); + } - resourcesWithLoaders.updateKey(newKey); - resourcesWithLoaders.resources() - .setImpl(createResourcesImpl(newKey)); + redirectResourcesToNewImplLocked(updatedResourceImplKeys); + } } } } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index f170b5dfbc27..59c171923f2b 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -33,8 +33,10 @@ import android.app.role.RoleControllerManager; import android.app.role.RoleManager; import android.app.slice.SliceManager; import android.app.timedetector.TimeDetector; +import android.app.timedetector.TimeDetectorImpl; import android.app.timezone.RulesManager; import android.app.timezonedetector.TimeZoneDetector; +import android.app.timezonedetector.TimeZoneDetectorImpl; import android.app.trust.TrustManager; import android.app.usage.IStorageStatsManager; import android.app.usage.IUsageStatsManager; @@ -1196,7 +1198,7 @@ public final class SystemServiceRegistry { @Override public TimeDetector createService(ContextImpl ctx) throws ServiceNotFoundException { - return new TimeDetector(); + return new TimeDetectorImpl(); }}); registerService(Context.TIME_ZONE_DETECTOR_SERVICE, TimeZoneDetector.class, @@ -1204,7 +1206,7 @@ public final class SystemServiceRegistry { @Override public TimeZoneDetector createService(ContextImpl ctx) throws ServiceNotFoundException { - return new TimeZoneDetector(); + return new TimeZoneDetectorImpl(); }}); registerService(Context.PERMISSION_SERVICE, PermissionManager.class, diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index 7c29f017c02b..2412fb3994ed 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -21,31 +21,29 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.ServiceManager.ServiceNotFoundException; import android.os.SystemClock; import android.os.TimestampedValue; -import android.util.Log; /** * The interface through which system components can send signals to the TimeDetectorService. * - * <p>This class is marked non-final for mockito. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.TIME_DETECTOR_SERVICE) -public class TimeDetector { - private static final String TAG = "timedetector.TimeDetector"; - private static final boolean DEBUG = false; +public interface TimeDetector { - private final ITimeDetectorService mITimeDetectorService; - - /** @hide */ - public TimeDetector() throws ServiceNotFoundException { - mITimeDetectorService = ITimeDetectorService.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.TIME_DETECTOR_SERVICE)); + /** + * A shared utility method to create a {@link ManualTimeSuggestion}. + * + * @hide + */ + static ManualTimeSuggestion createManualTimeSuggestion(long when, String why) { + TimestampedValue<Long> utcTime = + new TimestampedValue<>(SystemClock.elapsedRealtime(), when); + ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(utcTime); + manualTimeSuggestion.addDebugInfo(why); + return manualTimeSuggestion; } /** @@ -54,16 +52,7 @@ public class TimeDetector { * were determined more recently. */ @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE) - public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { - if (DEBUG) { - Log.d(TAG, "suggestPhoneTime called: " + timeSuggestion); - } - try { - mITimeDetectorService.suggestPhoneTime(timeSuggestion); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion); /** * Suggests the user's manually entered current time to the detector. @@ -71,29 +60,7 @@ public class TimeDetector { * @hide */ @RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE) - public void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion) { - if (DEBUG) { - Log.d(TAG, "suggestManualTime called: " + timeSuggestion); - } - try { - mITimeDetectorService.suggestManualTime(timeSuggestion); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * A shared utility method to create a {@link ManualTimeSuggestion}. - * - * @hide - */ - public static ManualTimeSuggestion createManualTimeSuggestion(long when, String why) { - TimestampedValue<Long> utcTime = - new TimestampedValue<>(SystemClock.elapsedRealtime(), when); - ManualTimeSuggestion manualTimeSuggestion = new ManualTimeSuggestion(utcTime); - manualTimeSuggestion.addDebugInfo(why); - return manualTimeSuggestion; - } + void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion); /** * Suggests the time according to a network time source like NTP. @@ -101,14 +68,5 @@ public class TimeDetector { * @hide */ @RequiresPermission(android.Manifest.permission.SET_TIME) - public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) { - if (DEBUG) { - Log.d(TAG, "suggestNetworkTime called: " + timeSuggestion); - } - try { - mITimeDetectorService.suggestNetworkTime(timeSuggestion); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion); } diff --git a/core/java/android/app/timedetector/TimeDetectorImpl.java b/core/java/android/app/timedetector/TimeDetectorImpl.java new file mode 100644 index 000000000000..1683817740c3 --- /dev/null +++ b/core/java/android/app/timedetector/TimeDetectorImpl.java @@ -0,0 +1,77 @@ +/* + * 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.timedetector; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.util.Log; + +/** + * The real implementation of {@link TimeDetector}. + * + * @hide + */ +public final class TimeDetectorImpl implements TimeDetector { + private static final String TAG = "timedetector.TimeDetector"; + private static final boolean DEBUG = false; + + private final ITimeDetectorService mITimeDetectorService; + + public TimeDetectorImpl() throws ServiceNotFoundException { + mITimeDetectorService = ITimeDetectorService.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.TIME_DETECTOR_SERVICE)); + } + + @Override + public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { + if (DEBUG) { + Log.d(TAG, "suggestPhoneTime called: " + timeSuggestion); + } + try { + mITimeDetectorService.suggestPhoneTime(timeSuggestion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion) { + if (DEBUG) { + Log.d(TAG, "suggestManualTime called: " + timeSuggestion); + } + try { + mITimeDetectorService.suggestManualTime(timeSuggestion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) { + if (DEBUG) { + Log.d(TAG, "suggestNetworkTime called: " + timeSuggestion); + } + try { + mITimeDetectorService.suggestNetworkTime(timeSuggestion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java index 5b5f311264e3..b4f608787d4a 100644 --- a/core/java/android/app/timezonedetector/TimeZoneDetector.java +++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java @@ -21,29 +21,25 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.ServiceManager.ServiceNotFoundException; -import android.util.Log; /** * The interface through which system components can send signals to the TimeZoneDetectorService. * - * <p>This class is non-final for mockito. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.TIME_ZONE_DETECTOR_SERVICE) -public class TimeZoneDetector { - private static final String TAG = "timezonedetector.TimeZoneDetector"; - private static final boolean DEBUG = false; +public interface TimeZoneDetector { - private final ITimeZoneDetectorService mITimeZoneDetectorService; - - /** @hide */ - public TimeZoneDetector() throws ServiceNotFoundException { - mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE)); + /** + * A shared utility method to create a {@link ManualTimeZoneSuggestion}. + * + * @hide + */ + static ManualTimeZoneSuggestion createManualTimeZoneSuggestion(String tzId, String debugInfo) { + ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(tzId); + suggestion.addDebugInfo(debugInfo); + return suggestion; } /** @@ -55,16 +51,7 @@ public class TimeZoneDetector { */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE) - public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) { - if (DEBUG) { - Log.d(TAG, "suggestPhoneTimeZone called: " + timeZoneSuggestion); - } - try { - mITimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion); /** * Suggests the current time zone, determined for the user's manually information, to the @@ -73,25 +60,5 @@ public class TimeZoneDetector { * @hide */ @RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE) - public void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) { - if (DEBUG) { - Log.d(TAG, "suggestManualTimeZone called: " + timeZoneSuggestion); - } - try { - mITimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * A shared utility method to create a {@link ManualTimeZoneSuggestion}. - * - * @hide - */ - public static ManualTimeZoneSuggestion createManualTimeZoneSuggestion(String tzId, String why) { - ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(tzId); - suggestion.addDebugInfo(why); - return suggestion; - } + void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion); } diff --git a/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.java new file mode 100644 index 000000000000..27b8374db172 --- /dev/null +++ b/core/java/android/app/timezonedetector/TimeZoneDetectorImpl.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.app.timezonedetector; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.util.Log; + +/** + * The real implementation of {@link TimeZoneDetector}. + * + * @hide + */ +public final class TimeZoneDetectorImpl implements TimeZoneDetector { + private static final String TAG = "timezonedetector.TimeZoneDetector"; + private static final boolean DEBUG = false; + + private final ITimeZoneDetectorService mITimeZoneDetectorService; + + public TimeZoneDetectorImpl() throws ServiceNotFoundException { + mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE)); + } + + @Override + public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) { + if (DEBUG) { + Log.d(TAG, "suggestPhoneTimeZone called: " + timeZoneSuggestion); + } + try { + mITimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) { + if (DEBUG) { + Log.d(TAG, "suggestManualTimeZone called: " + timeZoneSuggestion); + } + try { + mITimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index c271e3c66adc..e1942da8ac7f 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -49,6 +49,7 @@ import android.os.IBinder; import android.os.ICancellationSignal; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.Trace; import android.os.UserHandle; @@ -300,6 +301,13 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } @Override + public void getTypeAsync(Uri uri, RemoteCallback callback) { + final Bundle result = new Bundle(); + result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri)); + callback.sendResult(result); + } + + @Override public Uri insert(String callingPkg, @Nullable String featureId, Uri uri, ContentValues initialValues, Bundle extras) { uri = validateIncomingUri(uri); diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index 45ace4010961..0f1442d864ba 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -33,6 +33,7 @@ import android.os.ICancellationSignal; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.os.RemoteCallback; import android.os.RemoteException; import java.io.FileNotFoundException; @@ -146,6 +147,14 @@ abstract public class ContentProviderNative extends Binder implements IContentPr return true; } + case GET_TYPE_ASYNC_TRANSACTION: { + data.enforceInterface(IContentProvider.descriptor); + Uri url = Uri.CREATOR.createFromParcel(data); + RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data); + getTypeAsync(url, callback); + return true; + } + case INSERT_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); @@ -495,6 +504,22 @@ final class ContentProviderProxy implements IContentProvider } @Override + /* oneway */ public void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException { + Parcel data = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); + + uri.writeToParcel(data, 0); + callback.writeToParcel(data, 0); + + mRemote.transact(IContentProvider.GET_TYPE_ASYNC_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + } finally { + data.recycle(); + } + } + + @Override public Uri insert(String callingPkg, @Nullable String featureId, Uri url, ContentValues values, Bundle extras) throws RemoteException { diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 6cd1cd3354c2..f32a4ab43357 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -55,6 +55,7 @@ import android.os.IBinder; import android.os.ICancellationSignal; import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -67,6 +68,7 @@ import android.util.Log; import android.util.Size; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.MimeIconUtils; import dalvik.system.CloseGuard; @@ -695,6 +697,9 @@ public abstract class ContentResolver implements ContentInterface { private static final int SLOW_THRESHOLD_MILLIS = 500; private final Random mRandom = new Random(); // guarded by itself + /** @hide */ + public static final String REMOTE_CALLBACK_RESULT = "result"; + public ContentResolver(@Nullable Context context) { this(context, null); } @@ -807,7 +812,10 @@ public abstract class ContentResolver implements ContentInterface { IContentProvider provider = acquireExistingProvider(url); if (provider != null) { try { - return provider.getType(url); + final GetTypeResultListener resultListener = new GetTypeResultListener(); + provider.getTypeAsync(url, new RemoteCallback(resultListener)); + resultListener.waitForResult(); + return resultListener.type; } catch (RemoteException e) { // Arbitrary and not worth documenting, as Activity // Manager will kill this process shortly anyway. @@ -825,17 +833,53 @@ public abstract class ContentResolver implements ContentInterface { } try { - String type = ActivityManager.getService().getProviderMimeType( - ContentProvider.getUriWithoutUserId(url), resolveUserId(url)); - return type; + GetTypeResultListener resultListener = new GetTypeResultListener(); + ActivityManager.getService().getProviderMimeTypeAsync( + ContentProvider.getUriWithoutUserId(url), + resolveUserId(url), + new RemoteCallback(resultListener)); + resultListener.waitForResult(); + return resultListener.type; } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + // We just failed to send a oneway request to the System Server. Nothing to do. + return null; } catch (java.lang.Exception e) { Log.w(TAG, "Failed to get type for: " + url + " (" + e.getMessage() + ")"); return null; } } + private static final int GET_TYPE_TIMEOUT_MILLIS = 3000; + + private static class GetTypeResultListener implements RemoteCallback.OnResultListener { + @GuardedBy("this") + public boolean done; + + @GuardedBy("this") + public String type; + + @Override + public void onResult(Bundle result) { + synchronized (this) { + type = result.getString(REMOTE_CALLBACK_RESULT); + done = true; + notifyAll(); + } + } + + public void waitForResult() { + synchronized (this) { + if (!done) { + try { + wait(GET_TYPE_TIMEOUT_MILLIS); + } catch (InterruptedException e) { + // Ignore + } + } + } + } + } + /** * Query for the possible MIME types for the representations the given * content URL can be returned when opened as as stream with diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 6f477ff17c67..4658ba109d5f 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -27,6 +27,7 @@ import android.os.IBinder; import android.os.ICancellationSignal; import android.os.IInterface; import android.os.ParcelFileDescriptor; +import android.os.RemoteCallback; import android.os.RemoteException; import java.io.FileNotFoundException; @@ -42,6 +43,14 @@ public interface IContentProvider extends IInterface { @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) throws RemoteException; public String getType(Uri url) throws RemoteException; + + /** + * An oneway version of getType. The functionality is exactly the same, except that the + * call returns immediately, and the resulting type is returned when available via + * a binder callback. + */ + void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException; + @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link " + "ContentProviderClient#insert(android.net.Uri, android.content.ContentValues)} " @@ -152,4 +161,5 @@ public interface IContentProvider extends IInterface { static final int UNCANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 25; static final int REFRESH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 26; static final int CHECK_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 27; + int GET_TYPE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 28; } diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java index 4c10a8f7ad38..76007e67798d 100644 --- a/core/java/android/content/integrity/AtomicFormula.java +++ b/core/java/android/content/integrity/AtomicFormula.java @@ -205,6 +205,16 @@ public abstract class AtomicFormula extends IntegrityFormula { } @Override + public boolean isAppCertificateFormula() { + return false; + } + + @Override + public boolean isInstallerFormula() { + return false; + } + + @Override public String toString() { if (mValue == null || mOperator == null) { return String.format("(%s)", keyToString(getKey())); @@ -375,6 +385,16 @@ public abstract class AtomicFormula extends IntegrityFormula { } @Override + public boolean isAppCertificateFormula() { + return getKey() == APP_CERTIFICATE; + } + + @Override + public boolean isInstallerFormula() { + return getKey() == INSTALLER_NAME || getKey() == INSTALLER_CERTIFICATE; + } + + @Override public String toString() { if (mValue == null || mIsHashedValue == null) { return String.format("(%s)", keyToString(getKey())); @@ -531,6 +551,16 @@ public abstract class AtomicFormula extends IntegrityFormula { } @Override + public boolean isAppCertificateFormula() { + return false; + } + + @Override + public boolean isInstallerFormula() { + return false; + } + + @Override public String toString() { if (mValue == null) { return String.format("(%s)", keyToString(getKey())); diff --git a/core/java/android/content/integrity/CompoundFormula.java b/core/java/android/content/integrity/CompoundFormula.java index 56061df21388..14b1197d7f45 100644 --- a/core/java/android/content/integrity/CompoundFormula.java +++ b/core/java/android/content/integrity/CompoundFormula.java @@ -131,6 +131,16 @@ public final class CompoundFormula extends IntegrityFormula implements Parcelabl } @Override + public boolean isAppCertificateFormula() { + return getFormulas().stream().anyMatch(formula -> formula.isAppCertificateFormula()); + } + + @Override + public boolean isInstallerFormula() { + return getFormulas().stream().anyMatch(formula -> formula.isInstallerFormula()); + } + + @Override public String toString() { StringBuilder sb = new StringBuilder(); if (mFormulas.size() == 1) { diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java index 0660f93e9f01..8505d32e3f02 100644 --- a/core/java/android/content/integrity/IntegrityFormula.java +++ b/core/java/android/content/integrity/IntegrityFormula.java @@ -136,6 +136,21 @@ public abstract class IntegrityFormula { public abstract @Tag boolean matches(AppInstallMetadata appInstallMetadata); /** + * Returns true when the formula (or one of its atomic formulas) has app certificate as key. + * + * @hide + */ + public abstract @Tag boolean isAppCertificateFormula(); + + /** + * Returns true when the formula (or one of its atomic formulas) has installer package name + * or installer certificate as key. + * + * @hide + */ + public abstract @Tag boolean isInstallerFormula(); + + /** * Write an {@link IntegrityFormula} to {@link android.os.Parcel}. * * <p>This helper method is needed because non-final class/interface are not allowed to be diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 1aed9772700b..b998539dee9c 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -133,6 +133,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public int fullBackupContent = 0; /** + * <code>true</code> if the package is capable of presenting a unified interface representing + * multiple profiles. + * @hide + */ + public boolean crossProfile; + + /** * The default extra UI options for activities in this application. * Set from the {@link android.R.attr#uiOptions} attribute in the * activity's manifest. @@ -1382,6 +1389,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { pw.println(prefix + "fullBackupContent=" + (fullBackupContent < 0 ? "false" : "true")); } + pw.println("crossProfile=" + (crossProfile ? "true" : "false")); if (networkSecurityConfigRes != 0) { pw.println(prefix + "networkSecurityConfigRes=0x" + Integer.toHexString(networkSecurityConfigRes)); @@ -1586,6 +1594,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { uiOptions = orig.uiOptions; backupAgentName = orig.backupAgentName; fullBackupContent = orig.fullBackupContent; + crossProfile = orig.crossProfile; networkSecurityConfigRes = orig.networkSecurityConfigRes; category = orig.category; targetSandboxVersion = orig.targetSandboxVersion; @@ -1665,6 +1674,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(descriptionRes); dest.writeInt(uiOptions); dest.writeInt(fullBackupContent); + dest.writeBoolean(crossProfile); dest.writeInt(networkSecurityConfigRes); dest.writeInt(category); dest.writeInt(targetSandboxVersion); @@ -1741,6 +1751,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { descriptionRes = source.readInt(); uiOptions = source.readInt(); fullBackupContent = source.readInt(); + crossProfile = source.readBoolean(); networkSecurityConfigRes = source.readInt(); category = source.readInt(); targetSandboxVersion = source.readInt(); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index dbfc65066c11..430241aac072 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -8521,7 +8521,8 @@ public class PackageParser { Display.DEFAULT_DISPLAY, null, systemResources.getCompatibilityInfo(), - systemResources.getClassLoader()); + systemResources.getClassLoader(), + null); sUseRoundIcon = overlayableRes.getBoolean(com.android.internal.R.bool.config_useRoundIcon); } diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java index 9baf3258a230..fe8307c7c8cd 100644 --- a/core/java/android/content/pm/parsing/PackageImpl.java +++ b/core/java/android/content/pm/parsing/PackageImpl.java @@ -2382,6 +2382,7 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android appInfo.uiOptions = uiOptions; appInfo.volumeUuid = volumeUuid; appInfo.zygotePreloadName = zygotePreloadName; + appInfo.crossProfile = isCrossProfile(); appInfo.setBaseCodePath(baseCodePath); appInfo.setBaseResourcePath(baseCodePath); diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 96fbe9109c18..1b0175812949 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -27,13 +27,12 @@ import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; -import android.content.res.loader.ResourceLoader; -import android.content.res.loader.ResourceLoaderManager; +import android.content.res.loader.AssetsProvider; +import android.content.res.loader.ResourcesLoader; import android.content.res.loader.ResourcesProvider; import android.os.ParcelFileDescriptor; import android.util.ArraySet; import android.util.Log; -import android.util.Pair; import android.util.SparseArray; import android.util.TypedValue; @@ -47,6 +46,7 @@ import java.io.InputStream; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -113,12 +113,7 @@ public final class AssetManager implements AutoCloseable { @GuardedBy("this") private int mNumRefs = 1; @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks; - private ResourceLoaderManager mResourceLoaderManager; - - /** @hide */ - public void setResourceLoaderManager(ResourceLoaderManager resourceLoaderManager) { - mResourceLoaderManager = resourceLoaderManager; - } + private ResourcesLoader[] mLoaders; /** * A Builder class that helps create an AssetManager with only a single invocation of @@ -130,32 +125,66 @@ public final class AssetManager implements AutoCloseable { */ public static class Builder { private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>(); + private ArrayList<ResourcesLoader> mLoaders = new ArrayList<>(); public Builder addApkAssets(ApkAssets apkAssets) { mUserApkAssets.add(apkAssets); return this; } + public Builder addLoader(ResourcesLoader loader) { + mLoaders.add(loader); + return this; + } + public AssetManager build() { // Retrieving the system ApkAssets forces their creation as well. final ApkAssets[] systemApkAssets = getSystem().getApkAssets(); - final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size(); + // Filter ApkAssets so that assets provided by multiple loaders are only included once + // in the AssetManager assets. The last appearance of the ApkAssets dictates its load + // order. + final ArrayList<ApkAssets> loaderApkAssets = new ArrayList<>(); + final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>(); + for (int i = mLoaders.size() - 1; i >= 0; i--) { + final List<ApkAssets> currentLoaderApkAssets = mLoaders.get(i).getApkAssets(); + for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) { + final ApkAssets apkAssets = currentLoaderApkAssets.get(j); + if (uniqueLoaderApkAssets.contains(apkAssets)) { + continue; + } + + uniqueLoaderApkAssets.add(apkAssets); + loaderApkAssets.add(0, apkAssets); + } + } + + final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size() + + loaderApkAssets.size(); final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount]; System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length); - final int userApkAssetCount = mUserApkAssets.size(); - for (int i = 0; i < userApkAssetCount; i++) { + // Append user ApkAssets after system ApkAssets. + for (int i = 0, n = mUserApkAssets.size(); i < n; i++) { apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i); } + // Append ApkAssets provided by loaders to the end. + for (int i = 0, n = loaderApkAssets.size(); i < n; i++) { + apkAssets[i + systemApkAssets.length + mUserApkAssets.size()] = + loaderApkAssets.get(i); + } + // Calling this constructor prevents creation of system ApkAssets, which we took care // of in this Builder. final AssetManager assetManager = new AssetManager(false /*sentinel*/); assetManager.mApkAssets = apkAssets; AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets, false /*invalidateCaches*/); + assetManager.mLoaders = mLoaders.isEmpty() ? null + : mLoaders.toArray(new ResourcesLoader[0]); + return assetManager; } } @@ -432,6 +461,12 @@ public final class AssetManager implements AutoCloseable { } } + /** @hide */ + @NonNull + public List<ResourcesLoader> getLoaders() { + return mLoaders == null ? Collections.emptyList() : Arrays.asList(mLoaders); + } + /** * Ensures that the native implementation has not been destroyed. * The AssetManager may have been closed, but references to it still exist @@ -1056,38 +1091,70 @@ public final class AssetManager implements AutoCloseable { } } + private ResourcesProvider findResourcesProvider(int assetCookie) { + if (mLoaders == null) { + return null; + } + + int apkAssetsIndex = assetCookie - 1; + if (apkAssetsIndex >= mApkAssets.length || apkAssetsIndex < 0) { + return null; + } + + final ApkAssets apkAssets = mApkAssets[apkAssetsIndex]; + if (!apkAssets.isForLoader()) { + return null; + } + + for (int i = mLoaders.length - 1; i >= 0; i--) { + final ResourcesLoader loader = mLoaders[i]; + for (int j = 0, n = loader.getProviders().size(); j < n; j++) { + final ResourcesProvider provider = loader.getProviders().get(j); + if (apkAssets == provider.getApkAssets()) { + return provider; + } + } + } + + return null; + } + private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode) throws IOException { - if (mResourceLoaderManager == null) { + if (mLoaders == null) { return null; } - List<Pair<ResourceLoader, ResourcesProvider>> loaders = - mResourceLoaderManager.getInternalList(); - - // A cookie of 0 means no specific ApkAssets, so search everything if (cookie == 0) { - for (int index = loaders.size() - 1; index >= 0; index--) { - Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index); - try { - InputStream inputStream = pair.first.loadAsset(fileName, accessMode); - if (inputStream != null) { - return inputStream; + // A cookie of 0 means no specific ApkAssets, so search everything + for (int i = mLoaders.length - 1; i >= 0; i--) { + final ResourcesLoader loader = mLoaders[i]; + final List<ResourcesProvider> providers = loader.getProviders(); + for (int j = providers.size() - 1; j >= 0; j--) { + final AssetsProvider assetsProvider = providers.get(j).getAssetsProvider(); + if (assetsProvider == null) { + continue; + } + + try { + final InputStream inputStream = assetsProvider.loadAsset( + fileName, accessMode); + if (inputStream != null) { + return inputStream; + } + } catch (IOException ignored) { + // When searching, ignore read failures } - } catch (IOException ignored) { - // When searching, ignore read failures } } return null; } - ApkAssets apkAssets = mApkAssets[cookie - 1]; - for (int index = loaders.size() - 1; index >= 0; index--) { - Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index); - if (pair.second.getApkAssets() == apkAssets) { - return pair.first.loadAsset(fileName, accessMode); - } + final ResourcesProvider provider = findResourcesProvider(cookie); + if (provider != null && provider.getAssetsProvider() != null) { + return provider.getAssetsProvider().loadAsset( + fileName, accessMode); } return null; @@ -1095,43 +1162,48 @@ public final class AssetManager implements AutoCloseable { private AssetFileDescriptor searchLoadersFd(int cookie, @NonNull String fileName) throws IOException { - if (mResourceLoaderManager == null) { + if (mLoaders == null) { return null; } - List<Pair<ResourceLoader, ResourcesProvider>> loaders = - mResourceLoaderManager.getInternalList(); - - // A cookie of 0 means no specific ApkAssets, so search everything if (cookie == 0) { - for (int index = loaders.size() - 1; index >= 0; index--) { - Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index); - try { - ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName); - if (fileDescriptor != null) { - return new AssetFileDescriptor(fileDescriptor, 0, - AssetFileDescriptor.UNKNOWN_LENGTH); + // A cookie of 0 means no specific ApkAssets, so search everything + for (int i = mLoaders.length - 1; i >= 0; i--) { + final ResourcesLoader loader = mLoaders[i]; + final List<ResourcesProvider> providers = loader.getProviders(); + for (int j = providers.size() - 1; j >= 0; j--) { + final AssetsProvider assetsProvider = providers.get(j).getAssetsProvider(); + if (assetsProvider == null) { + continue; + } + + try { + final ParcelFileDescriptor fileDescriptor = assetsProvider + .loadAssetParcelFd(fileName); + if (fileDescriptor != null) { + return new AssetFileDescriptor(fileDescriptor, 0, + AssetFileDescriptor.UNKNOWN_LENGTH); + } + } catch (IOException ignored) { + // When searching, ignore read failures } - } catch (IOException ignored) { - // When searching, ignore read failures } } return null; } - ApkAssets apkAssets = mApkAssets[cookie - 1]; - for (int index = loaders.size() - 1; index >= 0; index--) { - Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index); - if (pair.second.getApkAssets() == apkAssets) { - ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName); - if (fileDescriptor != null) { - return new AssetFileDescriptor(fileDescriptor, 0, - AssetFileDescriptor.UNKNOWN_LENGTH); - } - return null; + final ResourcesProvider provider = findResourcesProvider(cookie); + if (provider != null && provider.getAssetsProvider() != null) { + final ParcelFileDescriptor fileDescriptor = provider.getAssetsProvider() + .loadAssetParcelFd(fileName); + if (fileDescriptor != null) { + return new AssetFileDescriptor(fileDescriptor, 0, + AssetFileDescriptor.UNKNOWN_LENGTH); } + return null; } + return null; } diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 4725e0af4eec..471e83c4c3eb 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -30,7 +30,6 @@ import android.annotation.DimenRes; import android.annotation.DrawableRes; import android.annotation.FontRes; import android.annotation.FractionRes; -import android.annotation.IntRange; import android.annotation.IntegerRes; import android.annotation.LayoutRes; import android.annotation.NonNull; @@ -41,13 +40,10 @@ import android.annotation.StringRes; import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.annotation.XmlRes; -import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; -import android.content.res.loader.ResourceLoader; -import android.content.res.loader.ResourceLoaderManager; -import android.content.res.loader.ResourcesProvider; +import android.content.res.loader.ResourcesLoader; import android.graphics.Movie; import android.graphics.Typeface; import android.graphics.drawable.Drawable; @@ -55,18 +51,17 @@ import android.graphics.drawable.Drawable.ConstantState; import android.graphics.drawable.DrawableInflater; import android.os.Build; import android.os.Bundle; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.LongSparseArray; -import android.util.Pair; import android.util.Pools.SynchronizedPool; import android.util.TypedValue; import android.view.DisplayAdjustments; import android.view.ViewDebug; import android.view.ViewHierarchyEncoder; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; @@ -117,6 +112,7 @@ public class Resources { static final String TAG = "Resources"; private static final Object sSync = new Object(); + private final Object mLock = new Object(); // Used by BridgeResources in layoutlib @UnsupportedAppUsage @@ -143,10 +139,7 @@ public class Resources { @UnsupportedAppUsage final ClassLoader mClassLoader; - private final Object mResourceLoaderLock = new Object(); - - @GuardedBy("mResourceLoaderLock") - private ResourceLoaderManager mResourceLoaderManager; + private UpdateCallbacks mCallbacks = null; /** * WeakReferences to Themes that were constructed from this Resources object. @@ -240,6 +233,18 @@ public class Resources { } } + /** @hide */ + public interface UpdateCallbacks extends ResourcesLoader.UpdateCallbacks { + /** + * Invoked when a {@link Resources} instance has a {@link ResourcesLoader} added, removed, + * or reordered. + * + * @param resources the instance being updated + * @param newLoaders the new set of loaders for the instance + */ + void onLoadersChanged(Resources resources, List<ResourcesLoader> newLoaders); + } + /** * Create a new Resources object on top of an existing set of assets in an * AssetManager. @@ -303,12 +308,6 @@ public class Resources { mBaseApkAssetsSize = ArrayUtils.size(impl.getAssets().getApkAssets()); mResourcesImpl = impl; - synchronized (mResourceLoaderLock) { - if (mResourceLoaderManager != null) { - mResourceLoaderManager.onImplUpdate(mResourcesImpl); - } - } - // Create new ThemeImpls that are identical to the ones we have. synchronized (mThemeRefs) { final int count = mThemeRefs.size(); @@ -322,6 +321,15 @@ public class Resources { } } + /** @hide */ + public void setCallbacks(UpdateCallbacks callbacks) { + if (mCallbacks != null) { + throw new IllegalStateException("callback already registered"); + } + + mCallbacks = callbacks; + } + /** * @hide */ @@ -937,14 +945,6 @@ public class Resources { @UnsupportedAppUsage Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme) throws NotFoundException { - ResourceLoader loader = findLoader(value.assetCookie); - if (loader != null) { - Drawable drawable = loader.loadDrawable(value, id, density, theme); - if (drawable != null) { - return drawable; - } - } - return mResourcesImpl.loadDrawable(this, value, id, density, theme); } @@ -2337,14 +2337,6 @@ public class Resources { @UnsupportedAppUsage XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws NotFoundException { - ResourceLoader loader = findLoader(assetCookie); - if (loader != null) { - XmlResourceParser xml = loader.loadXmlResourceParser(file, id); - if (xml != null) { - return xml; - } - } - return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type); } @@ -2371,136 +2363,106 @@ public class Resources { return theme.obtainStyledAttributes(set, attrs, 0, 0); } - private ResourceLoader findLoader(int assetCookie) { - ApkAssets[] apkAssetsArray = mResourcesImpl.getAssets().getApkAssets(); - int apkAssetsIndex = assetCookie - 1; - if (apkAssetsIndex < apkAssetsArray.length && apkAssetsIndex >= 0) { - ApkAssets apkAssets = apkAssetsArray[apkAssetsIndex]; - if (apkAssets.isForLoader()) { - List<Pair<ResourceLoader, ResourcesProvider>> loaders; - // Since we don't lock the entire resolution path anyways, - // only lock here instead of entire method. The list is copied - // and effectively a snapshot is used. - synchronized (mResourceLoaderLock) { - loaders = mResourceLoaderManager.getInternalList(); - } - - if (!ArrayUtils.isEmpty(loaders)) { - int size = loaders.size(); - for (int index = 0; index < size; index++) { - Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index); - if (pair.second.getApkAssets() == apkAssets) { - return pair.first; - } - } - } - } + private void checkCallbacksRegistered() { + if (mCallbacks == null) { + throw new IllegalArgumentException("Cannot modify resource loaders of Resources" + + " instances created outside of ResourcesManager"); } - - return null; } /** - * @return copied list of loaders and providers previously added + * Retrieves the list of loaders. + * + * <p>Loaders are listed in increasing precedence order. A loader will override the resources + * and assets of loaders listed before itself. */ @NonNull - public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() { - synchronized (mResourceLoaderLock) { - return mResourceLoaderManager == null - ? Collections.emptyList() - : mResourceLoaderManager.getLoaders(); - } + public List<ResourcesLoader> getLoaders() { + return mResourcesImpl.getAssets().getLoaders(); } /** - * Add a custom {@link ResourceLoader} which is added to the paths searched by - * {@link AssetManager} when resolving a resource. - * - * Resources are resolved as if the loader was a resource overlay, meaning the latest - * in the list, of equal or better config, is returned. - * - * {@link ResourcesProvider}s passed in here are not managed and a reference should be held - * to remove, re-use, or close them when necessary. + * Appends a loader to the end of the loader list. If the loader is already present in the + * loader list, the list will not be modified. * - * @param resourceLoader an interface used to resolve file paths for drawables/XML files; - * a reference should be kept to remove the loader if necessary - * @param resourcesProvider an .apk or .arsc file representation - * @param index where to add the loader in the list - * @throws IllegalArgumentException if the resourceLoader is already added - * @throws IndexOutOfBoundsException if the index is invalid + * @param loader the loader to add */ - public void addLoader(@NonNull ResourceLoader resourceLoader, - @NonNull ResourcesProvider resourcesProvider, @IntRange(from = 0) int index) { - synchronized (mResourceLoaderLock) { - if (mResourceLoaderManager == null) { - ResourcesManager.getInstance().registerForLoaders(this); - mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl); + public void addLoader(@NonNull ResourcesLoader loader) { + synchronized (mLock) { + checkCallbacksRegistered(); + + final List<ResourcesLoader> loaders = new ArrayList<>( + mResourcesImpl.getAssets().getLoaders()); + if (loaders.contains(loader)) { + return; } - mResourceLoaderManager.addLoader(resourceLoader, resourcesProvider, index); + loaders.add(loader); + mCallbacks.onLoadersChanged(this, loaders); + loader.registerOnProvidersChangedCallback(this, mCallbacks); } } /** - * @see #addLoader(ResourceLoader, ResourcesProvider, int). - * - * Adds to the end of the list. + * Removes a loader from the loaders. If the loader is not present in the loader list, the list + * will not be modified. * - * @return index the loader was added at + * @param loader the loader to remove */ - public int addLoader(@NonNull ResourceLoader resourceLoader, - @NonNull ResourcesProvider resourcesProvider) { - synchronized (mResourceLoaderLock) { - int index = getLoaders().size(); - addLoader(resourceLoader, resourcesProvider, index); - return index; - } - } + public void removeLoader(@NonNull ResourcesLoader loader) { + synchronized (mLock) { + checkCallbacksRegistered(); - /** - * Remove a loader previously added by - * {@link #addLoader(ResourceLoader, ResourcesProvider, int)} - * - * The caller maintains responsibility for holding a reference to the matching - * {@link ResourcesProvider} and closing it after this method has been called. - * - * @param resourceLoader the same reference passed into [addLoader - * @return the index the loader was at in the list, or -1 if the loader was not found - */ - public int removeLoader(@NonNull ResourceLoader resourceLoader) { - synchronized (mResourceLoaderLock) { - if (mResourceLoaderManager == null) { - return -1; + final List<ResourcesLoader> loaders = new ArrayList<>( + mResourcesImpl.getAssets().getLoaders()); + if (!loaders.remove(loader)) { + return; } - return mResourceLoaderManager.removeLoader(resourceLoader); + mCallbacks.onLoadersChanged(this, loaders); + loader.unregisterOnProvidersChangedCallback(this); } } /** - * Swap the current set of loaders. Preferred to multiple remove/add calls as this doesn't - * update the resource data structures after each modification. - * - * Set to null or an empty list to clear the set of loaders. + * Sets the list of loaders. * - * The caller maintains responsibility for holding references to the added - * {@link ResourcesProvider}s and closing them after this method has been called. - * - * @param resourceLoadersAndProviders a list of pairs to add + * @param loaders the new loaders */ - public void setLoaders( - @Nullable List<Pair<ResourceLoader, ResourcesProvider>> resourceLoadersAndProviders) { - synchronized (mResourceLoaderLock) { - if (mResourceLoaderManager == null) { - if (ArrayUtils.isEmpty(resourceLoadersAndProviders)) { - return; + public void setLoaders(@NonNull List<ResourcesLoader> loaders) { + synchronized (mLock) { + checkCallbacksRegistered(); + + final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders(); + int index = 0; + boolean modified = loaders.size() != oldLoaders.size(); + final ArraySet<ResourcesLoader> seenLoaders = new ArraySet<>(); + for (final ResourcesLoader loader : loaders) { + if (!seenLoaders.add(loader)) { + throw new IllegalArgumentException("Loader " + loader + " present twice"); + } + + if (!modified && oldLoaders.get(index++) != loader) { + modified = true; } + } - ResourcesManager.getInstance().registerForLoaders(this); - mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl); + if (!modified) { + return; } - mResourceLoaderManager.setLoaders(resourceLoadersAndProviders); + mCallbacks.onLoadersChanged(this, loaders); + for (int i = 0, n = oldLoaders.size(); i < n; i++) { + oldLoaders.get(i).unregisterOnProvidersChangedCallback(this); + } + for (ResourcesLoader newLoader : loaders) { + newLoader.registerOnProvidersChangedCallback(this, mCallbacks); + } } } + + /** Removes all {@link ResourcesLoader ResourcesLoader(s)}. */ + public void clearLoaders() { + setLoaders(Collections.emptyList()); + } } diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java index a29fea09c5ce..9e40f46c3c21 100644 --- a/core/java/android/content/res/ResourcesKey.java +++ b/core/java/android/content/res/ResourcesKey.java @@ -19,6 +19,7 @@ package android.content.res; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.content.res.loader.ResourcesLoader; import android.text.TextUtils; import java.util.Arrays; @@ -48,6 +49,9 @@ public final class ResourcesKey { @NonNull public final CompatibilityInfo mCompatInfo; + @Nullable + public final ResourcesLoader[] mLoaders; + private final int mHash; @UnsupportedAppUsage @@ -57,11 +61,13 @@ public final class ResourcesKey { @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, - @Nullable CompatibilityInfo compatInfo) { + @Nullable CompatibilityInfo compatInfo, + @Nullable ResourcesLoader[] loader) { mResDir = resDir; mSplitResDirs = splitResDirs; mOverlayDirs = overlayDirs; mLibDirs = libDirs; + mLoaders = (loader != null && loader.length == 0) ? null : loader; mDisplayId = displayId; mOverrideConfiguration = new Configuration(overrideConfig != null ? overrideConfig : Configuration.EMPTY); @@ -75,6 +81,7 @@ public final class ResourcesKey { hash = 31 * hash + mDisplayId; hash = 31 * hash + Objects.hashCode(mOverrideConfiguration); hash = 31 * hash + Objects.hashCode(mCompatInfo); + hash = 31 * hash + Arrays.hashCode(mLoaders); mHash = hash; } @@ -140,6 +147,9 @@ public final class ResourcesKey { if (!Objects.equals(mCompatInfo, peer.mCompatInfo)) { return false; } + if (!Arrays.equals(mLoaders, peer.mLoaders)) { + return false; + } return true; } @@ -167,7 +177,11 @@ public final class ResourcesKey { builder.append(" mOverrideConfig=").append(Configuration.resourceQualifierString( mOverrideConfiguration)); builder.append(" mCompatInfo=").append(mCompatInfo); - builder.append("}"); + builder.append(" mLoaders=["); + if (mLoaders != null) { + builder.append(TextUtils.join(",", mLoaders)); + } + builder.append("]}"); return builder.toString(); } } diff --git a/core/java/android/content/res/loader/AssetsProvider.java b/core/java/android/content/res/loader/AssetsProvider.java new file mode 100644 index 000000000000..c315494cf728 --- /dev/null +++ b/core/java/android/content/res/loader/AssetsProvider.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 android.content.res.loader; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.AssetManager; +import android.os.ParcelFileDescriptor; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Provides callbacks that allow for the value of a file-based resources or assets of a + * {@link ResourcesProvider} to be specified or overridden. + */ +public interface AssetsProvider { + + /** + * Callback that allows the value of a file-based resources or asset to be specified or + * overridden. + * + * <p>There are two situations in which this method will be called: + * <ul> + * <li>AssetManager is queried for an InputStream of an asset using APIs like + * {@link AssetManager#open} and {@link AssetManager#openXmlResourceParser}. + * <li>AssetManager is resolving the value of a file-based resource provided by the + * {@link ResourcesProvider} this instance is associated with. + * </ul> + * + * <p>If the value retrieved from this callback is null, AssetManager will attempt to find the + * file-based resource or asset within the APK provided by the ResourcesProvider this instance + * is associated with. + * + * @param path the asset path being loaded + * @param accessMode the {@link AssetManager} access mode + * + * @see AssetManager#open + */ + @Nullable + default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException { + return null; + } + + /** + * {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}. + * + * @param path the asset path being loaded + */ + @Nullable + default ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException { + return null; + } +} diff --git a/core/java/android/content/res/loader/DirectoryResourceLoader.java b/core/java/android/content/res/loader/DirectoryAssetsProvider.java index 7d90e72ab07e..81c2a4c1b4d6 100644 --- a/core/java/android/content/res/loader/DirectoryResourceLoader.java +++ b/core/java/android/content/res/loader/DirectoryAssetsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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. @@ -26,23 +26,27 @@ import java.io.IOException; import java.io.InputStream; /** - * A {@link ResourceLoader} that searches a directory for assets. - * - * Assumes that resource paths are resolvable child paths of the directory passed in. + * A {@link AssetsProvider} that searches a directory for assets. + * Assumes that resource paths are resolvable child paths of the root directory passed in. */ -public class DirectoryResourceLoader implements ResourceLoader { +public class DirectoryAssetsProvider implements AssetsProvider { @NonNull private final File mDirectory; - public DirectoryResourceLoader(@NonNull File directory) { + /** + * Creates a DirectoryAssetsProvider with given root directory. + * + * @param directory the root directory to resolve files from + */ + public DirectoryAssetsProvider(@NonNull File directory) { this.mDirectory = directory; } @Nullable @Override public InputStream loadAsset(@NonNull String path, int accessMode) throws IOException { - File file = findFile(path); + final File file = findFile(path); if (file == null || !file.exists()) { return null; } @@ -51,8 +55,8 @@ public class DirectoryResourceLoader implements ResourceLoader { @Nullable @Override - public ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException { - File file = findFile(path); + public ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException { + final File file = findFile(path); if (file == null || !file.exists()) { return null; } @@ -60,7 +64,9 @@ public class DirectoryResourceLoader implements ResourceLoader { } /** - * Find the file for the given path encoded into the resource table. + * Finds the file relative to the root directory. + * + * @param path the relative path of the file */ @Nullable public File findFile(@NonNull String path) { diff --git a/core/java/android/content/res/loader/ResourceLoader.java b/core/java/android/content/res/loader/ResourceLoader.java deleted file mode 100644 index af32aa2c6875..000000000000 --- a/core/java/android/content/res/loader/ResourceLoader.java +++ /dev/null @@ -1,116 +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 android.content.res.loader; - -import android.annotation.AnyRes; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.content.res.XmlResourceParser; -import android.graphics.drawable.Drawable; -import android.os.ParcelFileDescriptor; -import android.util.TypedValue; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Exposes methods for overriding file-based resource loading from a {@link Resources}. - * - * To be used with {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)} and related - * methods to override resource loading. - * - * Note that this class doesn't actually contain any resource data. Non-file-based resources are - * loaded directly from the {@link ResourcesProvider}'s .arsc representation. - * - * An instance's methods will only be called if its corresponding {@link ResourcesProvider}'s - * resources table contains an entry for the resource ID being resolved, - * with the exception of the non-cookie variants of {@link AssetManager}'s openAsset and - * openNonAsset. - * - * Those methods search backwards through all {@link ResourceLoader}s and then any paths provided - * by the application or system. - * - * Otherwise, an ARSC that defines R.drawable.some_id must be provided if a {@link ResourceLoader} - * wants to point R.drawable.some_id to a different file on disk. - */ -public interface ResourceLoader { - - /** - * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to - * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return a - * {@link Drawable} which should be returned by the parent - * {@link Resources#getDrawable(int, Resources.Theme)}. - * - * @param value the resolved {@link TypedValue} before it has been converted to a Drawable - * object - * @param id the R.drawable ID this resolution is for - * @param density the requested density - * @param theme the {@link Resources.Theme} resolved under - * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}, - * including calling through to {@link #loadAsset(String, int)} or {@link #loadAssetFd(String)} - */ - @Nullable - default Drawable loadDrawable(@NonNull TypedValue value, int id, int density, - @Nullable Resources.Theme theme) { - return null; - } - - /** - * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to - * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an - * {@link XmlResourceParser} which should be returned by the parent - * {@link Resources#getDrawable(int, Resources.Theme)}. - * - * @param path the string that was found in the string pool - * @param id the XML ID this resolution is for, can be R.anim, R.layout, or R.xml - * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}, - * including calling through to {@link #loadAssetFd(String)} (String, int)} - */ - @Nullable - default XmlResourceParser loadXmlResourceParser(@NonNull String path, @AnyRes int id) { - return null; - } - - /** - * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to - * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an - * {@link InputStream} which should be returned when an asset is loaded by {@link AssetManager}. - * Assets will be loaded from a provider's root, with anything in its assets subpath prefixed - * with "assets/". - * - * @param path the asset path to load - * @param accessMode {@link AssetManager} access mode; does not have to be respected - * @return null if resolution should try to find an entry inside the {@link ResourcesProvider} - */ - @Nullable - default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException { - return null; - } - - /** - * {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}. - * - * @param path the asset path to load - * @return null if resolution should try to find an entry inside the {@link ResourcesProvider} - */ - @Nullable - default ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException { - return null; - } -} diff --git a/core/java/android/content/res/loader/ResourceLoaderManager.java b/core/java/android/content/res/loader/ResourceLoaderManager.java deleted file mode 100644 index 592ec09aa730..000000000000 --- a/core/java/android/content/res/loader/ResourceLoaderManager.java +++ /dev/null @@ -1,190 +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 android.content.res.loader; - -import android.annotation.Nullable; -import android.content.res.ApkAssets; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.content.res.ResourcesImpl; -import android.util.Pair; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.ArrayUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * @hide - */ -public class ResourceLoaderManager { - - private final Object mLock = new Object(); - - @GuardedBy("mLock") - private final List<Pair<ResourceLoader, ResourcesProvider>> mResourceLoaders = - new ArrayList<>(); - - @GuardedBy("mLock") - private ResourcesImpl mResourcesImpl; - - public ResourceLoaderManager(ResourcesImpl resourcesImpl) { - this.mResourcesImpl = resourcesImpl; - this.mResourcesImpl.getAssets().setResourceLoaderManager(this); - } - - /** - * Copies the list to ensure that ongoing mutations don't affect the list if it's being used - * as a search set. - * - * @see Resources#getLoaders() - */ - public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() { - synchronized (mLock) { - return new ArrayList<>(mResourceLoaders); - } - } - - /** - * Returns a list for searching for a loader. Locks and copies the list to ensure that - * ongoing mutations don't affect the search set. - */ - public List<Pair<ResourceLoader, ResourcesProvider>> getInternalList() { - synchronized (mLock) { - return new ArrayList<>(mResourceLoaders); - } - } - - /** - * TODO(b/136251855): Consider optional boolean ignoreConfigurations to allow ResourceLoader - * to override every configuration in the target package - * - * @see Resources#addLoader(ResourceLoader, ResourcesProvider) - */ - public void addLoader(ResourceLoader resourceLoader, ResourcesProvider resourcesProvider, - int index) { - synchronized (mLock) { - for (int listIndex = 0; listIndex < mResourceLoaders.size(); listIndex++) { - if (Objects.equals(mResourceLoaders.get(listIndex).first, resourceLoader)) { - throw new IllegalArgumentException("Cannot add the same ResourceLoader twice"); - } - } - - mResourceLoaders.add(index, Pair.create(resourceLoader, resourcesProvider)); - updateLoaders(); - } - } - - /** - * @see Resources#removeLoader(ResourceLoader) - */ - public int removeLoader(ResourceLoader resourceLoader) { - synchronized (mLock) { - int indexOfLoader = -1; - - for (int index = 0; index < mResourceLoaders.size(); index++) { - if (mResourceLoaders.get(index).first == resourceLoader) { - indexOfLoader = index; - break; - } - } - - if (indexOfLoader < 0) { - return indexOfLoader; - } - - mResourceLoaders.remove(indexOfLoader); - updateLoaders(); - return indexOfLoader; - } - } - - /** - * @see Resources#setLoaders(List) - */ - public void setLoaders( - @Nullable List<Pair<ResourceLoader, ResourcesProvider>> newLoadersAndProviders) { - synchronized (mLock) { - if (ArrayUtils.isEmpty(newLoadersAndProviders)) { - mResourceLoaders.clear(); - updateLoaders(); - return; - } - - int size = newLoadersAndProviders.size(); - for (int newIndex = 0; newIndex < size; newIndex++) { - ResourceLoader resourceLoader = newLoadersAndProviders.get(newIndex).first; - for (int oldIndex = 0; oldIndex < mResourceLoaders.size(); oldIndex++) { - if (Objects.equals(mResourceLoaders.get(oldIndex).first, resourceLoader)) { - throw new IllegalArgumentException( - "Cannot add the same ResourceLoader twice"); - } - } - } - - mResourceLoaders.clear(); - mResourceLoaders.addAll(newLoadersAndProviders); - - updateLoaders(); - } - } - - /** - * Swap the tracked {@link ResourcesImpl} and reattach any loaders to it. - */ - public void onImplUpdate(ResourcesImpl resourcesImpl) { - synchronized (mLock) { - this.mResourcesImpl = resourcesImpl; - this.mResourcesImpl.getAssets().setResourceLoaderManager(this); - updateLoaders(); - } - } - - private void updateLoaders() { - synchronized (mLock) { - AssetManager assetManager = mResourcesImpl.getAssets(); - ApkAssets[] existingApkAssets = assetManager.getApkAssets(); - int baseApkAssetsSize = 0; - for (int index = existingApkAssets.length - 1; index >= 0; index--) { - // Loaders are always last, so the first non-loader is the end of the base assets - if (!existingApkAssets[index].isForLoader()) { - baseApkAssetsSize = index + 1; - break; - } - } - - List<ApkAssets> newAssets = new ArrayList<>(); - for (int index = 0; index < baseApkAssetsSize; index++) { - newAssets.add(existingApkAssets[index]); - } - - int size = mResourceLoaders.size(); - for (int index = 0; index < size; index++) { - ApkAssets apkAssets = mResourceLoaders.get(index).second.getApkAssets(); - newAssets.add(apkAssets); - } - - assetManager.setApkAssets(newAssets.toArray(new ApkAssets[0]), true); - - // Short of resolving every resource, it's too difficult to determine what has changed - // when a resource loader is changed, so just clear everything. - mResourcesImpl.clearAllCaches(); - } - } -} diff --git a/core/java/android/content/res/loader/ResourcesLoader.java b/core/java/android/content/res/loader/ResourcesLoader.java new file mode 100644 index 000000000000..69daceeaffc2 --- /dev/null +++ b/core/java/android/content/res/loader/ResourcesLoader.java @@ -0,0 +1,253 @@ +/* + * 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.content.res.loader; + +import android.annotation.NonNull; +import android.content.res.ApkAssets; +import android.content.res.Resources; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; + +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A container for supplying {@link ResourcesProvider ResourcesProvider(s)} to {@link Resources} + * objects. + * + * <p>{@link ResourcesLoader ResourcesLoader(s)} are added to Resources objects to supply + * additional resources and assets or modify the values of existing resources and assets. Multiple + * Resources objects can share the same ResourcesLoaders and ResourcesProviders. Changes to the list + * of {@link ResourcesProvider ResourcesProvider(s)} a loader contains propagates to all Resources + * objects that use the loader. + * + * <p>Loaders retrieved with {@link Resources#getLoaders()} are listed in increasing precedence + * order. A loader will override the resources and assets of loaders listed before itself. + * + * <p>Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A + * provider will override the resources and assets of providers listed before itself. + */ +public class ResourcesLoader { + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private ApkAssets[] mApkAssets; + + @GuardedBy("mLock") + private ResourcesProvider[] mPreviousProviders; + + @GuardedBy("mLock") + private ResourcesProvider[] mProviders; + + @GuardedBy("mLock") + private ArrayMap<WeakReference<Object>, UpdateCallbacks> mChangeCallbacks = new ArrayMap<>(); + + /** @hide */ + public interface UpdateCallbacks { + + /** + * Invoked when a {@link ResourcesLoader} has a {@link ResourcesProvider} added, removed, + * or reordered. + * + * @param loader the loader that was updated + */ + void onLoaderUpdated(@NonNull ResourcesLoader loader); + } + + /** + * Retrieves the list of providers loaded into this instance. Providers are listed in increasing + * precedence order. A provider will override the values of providers listed before itself. + */ + @NonNull + public List<ResourcesProvider> getProviders() { + synchronized (mLock) { + return mProviders == null ? Collections.emptyList() : Arrays.asList(mProviders); + } + } + + /** + * Appends a provider to the end of the provider list. If the provider is already present in the + * loader list, the list will not be modified. + * + * @param resourcesProvider the provider to add + */ + public void addProvider(@NonNull ResourcesProvider resourcesProvider) { + synchronized (mLock) { + mProviders = ArrayUtils.appendElement(ResourcesProvider.class, mProviders, + resourcesProvider); + notifyProvidersChangedLocked(); + } + } + + /** + * Removes a provider from the provider list. If the provider is not present in the provider + * list, the list will not be modified. + * + * @param resourcesProvider the provider to remove + */ + public void removeProvider(@NonNull ResourcesProvider resourcesProvider) { + synchronized (mLock) { + mProviders = ArrayUtils.removeElement(ResourcesProvider.class, mProviders, + resourcesProvider); + notifyProvidersChangedLocked(); + } + } + + /** + * Sets the list of providers. + * + * @param resourcesProviders the new providers + */ + public void setProviders(@NonNull List<ResourcesProvider> resourcesProviders) { + synchronized (mLock) { + mProviders = resourcesProviders.toArray(new ResourcesProvider[0]); + notifyProvidersChangedLocked(); + } + } + + /** Removes all {@link ResourcesProvider ResourcesProvider(s)}. */ + public void clearProviders() { + synchronized (mLock) { + mProviders = null; + notifyProvidersChangedLocked(); + } + } + + /** + * Retrieves the list of {@link ApkAssets} used by the providers. + * + * @hide + */ + @NonNull + public List<ApkAssets> getApkAssets() { + synchronized (mLock) { + if (mApkAssets == null) { + return Collections.emptyList(); + } + return Arrays.asList(mApkAssets); + } + } + + /** + * Registers a callback to be invoked when {@link ResourcesProvider ResourcesProvider(s)} + * change. + * @param instance the instance tied to the callback + * @param callbacks the callback to invoke + * + * @hide + */ + public void registerOnProvidersChangedCallback(@NonNull Object instance, + @NonNull UpdateCallbacks callbacks) { + synchronized (mLock) { + mChangeCallbacks.put(new WeakReference<>(instance), callbacks); + } + } + + /** + * Removes a previously registered callback. + * @param instance the instance tied to the callback + * + * @hide + */ + public void unregisterOnProvidersChangedCallback(@NonNull Object instance) { + synchronized (mLock) { + for (int i = 0, n = mChangeCallbacks.size(); i < n; i++) { + final WeakReference<Object> key = mChangeCallbacks.keyAt(i); + if (instance == key.get()) { + mChangeCallbacks.removeAt(i); + return; + } + } + } + } + + /** Returns whether the arrays contain the same provider instances in the same order. */ + private static boolean arrayEquals(ResourcesProvider[] a1, ResourcesProvider[] a2) { + if (a1 == a2) { + return true; + } + + if (a1 == null || a2 == null) { + return false; + } + + if (a1.length != a2.length) { + return false; + } + + // Check that the arrays contain the exact same instances in the same order. Providers do + // not have any form of equivalence checking of whether the contents of two providers have + // equivalent apk assets. + for (int i = 0, n = a1.length; i < n; i++) { + if (a1[i] != a2[i]) { + return false; + } + } + + return true; + } + + + /** + * Invokes registered callbacks when the list of {@link ResourcesProvider} instances this loader + * uses changes. + */ + private void notifyProvidersChangedLocked() { + final ArraySet<UpdateCallbacks> uniqueCallbacks = new ArraySet<>(); + if (arrayEquals(mPreviousProviders, mProviders)) { + return; + } + + if (mProviders == null || mProviders.length == 0) { + mApkAssets = null; + } else { + mApkAssets = new ApkAssets[mProviders.length]; + for (int i = 0, n = mProviders.length; i < n; i++) { + mProviders[i].incrementRefCount(); + mApkAssets[i] = mProviders[i].getApkAssets(); + } + } + + // Decrement the ref count after incrementing the new provider ref count so providers + // present before and after this method do not drop to zero references. + if (mPreviousProviders != null) { + for (ResourcesProvider provider : mPreviousProviders) { + provider.decrementRefCount(); + } + } + + mPreviousProviders = mProviders; + + for (int i = mChangeCallbacks.size() - 1; i >= 0; i--) { + final WeakReference<Object> key = mChangeCallbacks.keyAt(i); + if (key.get() == null) { + mChangeCallbacks.removeAt(i); + } else { + uniqueCallbacks.add(mChangeCallbacks.valueAt(i)); + } + } + + for (int i = 0, n = uniqueCallbacks.size(); i < n; i++) { + uniqueCallbacks.valueAt(i).onLoaderUpdated(this); + } + } +} diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java index 050aeb7c5fda..419ec7882f3d 100644 --- a/core/java/android/content/res/loader/ResourcesProvider.java +++ b/core/java/android/content/res/loader/ResourcesProvider.java @@ -17,84 +17,144 @@ package android.content.res.loader; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.ApkAssets; -import android.content.res.Resources; import android.os.ParcelFileDescriptor; import android.os.SharedMemory; +import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import java.io.Closeable; import java.io.IOException; /** - * Provides methods to load resources from an .apk or .arsc file to pass to - * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}. - * - * It is the responsibility of the app to close any instances. + * Provides methods to load resources data from APKs ({@code .apk}) and resources tables + * {@code .arsc} for use with {@link ResourcesLoader ResourcesLoader(s)}. */ -public final class ResourcesProvider implements AutoCloseable, Closeable { +public class ResourcesProvider implements AutoCloseable, Closeable { + private static final String TAG = "ResourcesProvider"; + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private boolean mOpen = true; + + @GuardedBy("mLock") + private int mOpenCount = 0; + + @GuardedBy("mLock") + private final ApkAssets mApkAssets; + + private final AssetsProvider mAssetsProvider; /** - * Contains no data, assuming that any resource loading behavior will be handled in the - * corresponding {@link ResourceLoader}. + * Creates an empty ResourcesProvider with no resource data. This is useful for loading assets + * that are not associated with resource identifiers. + * + * @param assetsProvider the assets provider that overrides the loading of file-based resources */ @NonNull - public static ResourcesProvider empty() { - return new ResourcesProvider(ApkAssets.loadEmptyForLoader()); + public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) { + return new ResourcesProvider(ApkAssets.loadEmptyForLoader(), assetsProvider); } /** - * Read from an .apk file descriptor. + * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor. + * + * The file descriptor is duplicated and the original may be closed by the application at any + * time without affecting the ResourcesProvider. * - * The file descriptor is duplicated and the one passed in may be closed by the application - * at any time. + * @param fileDescriptor the file descriptor of the APK to load */ @NonNull public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor) throws IOException { + return loadFromApk(fileDescriptor, null); + } + + /** + * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor. + * + * The file descriptor is duplicated and the original may be closed by the application at any + * time without affecting the ResourcesProvider. + * + * @param fileDescriptor the file descriptor of the APK to load + * @param assetsProvider the assets provider that overrides the loading of file-based resources + */ + @NonNull + public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor, + @Nullable AssetsProvider assetsProvider) + throws IOException { return new ResourcesProvider( - ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor())); + ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()), assetsProvider); } /** - * Read from an .apk file representation in memory. + * Creates a ResourcesProvider from an {@code .apk} file representation in memory. + * + * @param sharedMemory the shared memory containing the data of the APK to load */ @NonNull public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory) throws IOException { + return loadFromApk(sharedMemory, null); + } + + /** + * Creates a ResourcesProvider from an {@code .apk} file representation in memory. + * + * @param sharedMemory the shared memory containing the data of the APK to load + * @param assetsProvider the assets provider that implements the loading of file-based resources + */ + @NonNull + public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory, + @Nullable AssetsProvider assetsProvider) + throws IOException { return new ResourcesProvider( - ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor())); + ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()), assetsProvider); } /** - * Read from an .arsc file descriptor. + * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor. + * + * The file descriptor is duplicated and the original may be closed by the application at any + * time without affecting the ResourcesProvider. * - * The file descriptor is duplicated and the one passed in may be closed by the application - * at any time. + * @param fileDescriptor the file descriptor of the resources table to load + * @param assetsProvider the assets provider that implements the loading of file-based resources */ @NonNull - public static ResourcesProvider loadFromArsc(@NonNull ParcelFileDescriptor fileDescriptor) + public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor, + @Nullable AssetsProvider assetsProvider) throws IOException { return new ResourcesProvider( - ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor())); + ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()), assetsProvider); } /** - * Read from an .arsc file representation in memory. + * Creates a ResourcesProvider from a resources table ({@code .arsc}) file representation in + * memory. + * + * @param sharedMemory the shared memory containing the data of the resources table to load + * @param assetsProvider the assets provider that overrides the loading of file-based resources */ @NonNull - public static ResourcesProvider loadFromArsc(@NonNull SharedMemory sharedMemory) + public static ResourcesProvider loadFromTable(@NonNull SharedMemory sharedMemory, + @Nullable AssetsProvider assetsProvider) throws IOException { return new ResourcesProvider( - ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor())); + ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()), assetsProvider); } /** * Read from a split installed alongside the application, which may not have been * loaded initially because the application requested isolated split loading. + * + * @param context a context of the package that contains the split + * @param splitName the name of the split to load */ @NonNull public static ResourcesProvider loadFromSplit(@NonNull Context context, @@ -106,15 +166,18 @@ public final class ResourcesProvider implements AutoCloseable, Closeable { } String splitPath = appInfo.getSplitCodePaths()[splitIndex]; - return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath)); + return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath), null); } - - @NonNull - private final ApkAssets mApkAssets; - - private ResourcesProvider(@NonNull ApkAssets apkAssets) { + private ResourcesProvider(@NonNull ApkAssets apkAssets, + @Nullable AssetsProvider assetsProvider) { this.mApkAssets = apkAssets; + this.mAssetsProvider = assetsProvider; + } + + @Nullable + public AssetsProvider getAssetsProvider() { + return mAssetsProvider; } /** @hide */ @@ -123,8 +186,41 @@ public final class ResourcesProvider implements AutoCloseable, Closeable { return mApkAssets; } + final void incrementRefCount() { + synchronized (mLock) { + if (!mOpen) { + throw new IllegalStateException("Operation failed: resources provider is closed"); + } + mOpenCount++; + } + } + + final void decrementRefCount() { + synchronized (mLock) { + mOpenCount--; + } + } + + /** + * Frees internal data structures. Closed providers can no longer be added to + * {@link ResourcesLoader ResourcesLoader(s)}. + * + * @throws IllegalStateException if provider is currently used by a ResourcesLoader + */ @Override public void close() { + synchronized (mLock) { + if (!mOpen) { + return; + } + + if (mOpenCount != 0) { + throw new IllegalStateException("Failed to close provider used by " + mOpenCount + + " ResourcesLoader instances"); + } + mOpen = false; + } + try { mApkAssets.close(); } catch (Throwable ignored) { @@ -133,7 +229,16 @@ public final class ResourcesProvider implements AutoCloseable, Closeable { @Override protected void finalize() throws Throwable { - close(); - super.finalize(); + synchronized (mLock) { + if (mOpenCount != 0) { + Log.w(TAG, "ResourcesProvider " + this + " finalized with non-zero refs: " + + mOpenCount); + } + + if (mOpen) { + mOpen = false; + mApkAssets.close(); + } + } } } diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 1001f800df3c..db16d24e0af1 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -19,6 +19,7 @@ import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -26,6 +27,7 @@ import android.annotation.SystemService; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; @@ -52,6 +54,7 @@ import java.util.concurrent.Executor; */ @SystemApi @SystemService(Context.CONTEXTHUB_SERVICE) +@RequiresFeature(PackageManager.FEATURE_CONTEXTHUB) public final class ContextHubManager { private static final String TAG = "ContextHubManager"; diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index 152141edb52d..f2e16b46422f 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -42,7 +42,7 @@ import java.lang.annotation.RetentionPolicy; */ @SystemApi @SystemService(Context.BATTERY_STATS_SERVICE) -public class BatteryStatsManager { +public final class BatteryStatsManager { /** * Wifi states. * diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl index 416d69229536..e201e43b5586 100644 --- a/core/java/android/os/IVibratorService.aidl +++ b/core/java/android/os/IVibratorService.aidl @@ -24,6 +24,8 @@ interface IVibratorService { boolean hasVibrator(); boolean hasAmplitudeControl(); + boolean[] areEffectsSupported(in int[] effectIds); + boolean[] arePrimitivesSupported(in int[] primitiveIds); boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in VibrationEffect effect, in VibrationAttributes attributes); void vibrate(int uid, String opPkg, in VibrationEffect effect, diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 6a80788c81cc..f3d3837bbed1 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1371,6 +1371,7 @@ public final class PowerManager { * @throws UnsupportedOperationException if userspace reboot was requested on a device that * doesn't support it. */ + @RequiresPermission(permission.REBOOT) public void reboot(@Nullable String reason) { if (REBOOT_USERSPACE.equals(reason) && !isRebootingUserspaceSupported()) { throw new UnsupportedOperationException( @@ -1390,6 +1391,7 @@ public final class PowerManager { * </p> * @hide */ + @RequiresPermission(permission.REBOOT) public void rebootSafeMode() { try { mService.rebootSafeMode(false, true); diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 8050454a8ac3..faf4a36ff577 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.media.AudioAttributes; @@ -104,6 +105,28 @@ public class SystemVibrator extends Vibrator { } @Override + public boolean[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) { + try { + return mService.areEffectsSupported(effectIds); + } catch (RemoteException e) { + Log.w(TAG, "Failed to query effect support"); + } + return new boolean[effectIds.length]; + } + + @Override + public boolean[] arePrimitivesSupported( + @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + try { + return mService.arePrimitivesSupported(primitiveIds); + } catch (RemoteException e) { + Log.w(TAG, "Failed to query effect support"); + } + return new boolean[primitiveIds.length]; + } + + + @Override public void cancel() { if (mService == null) { return; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 08e4c3ae3683..3564589e35a4 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -290,10 +290,12 @@ public class UserManager { /** * Specifies if airplane mode is disallowed on the device. - * - * <p> This restriction can only be set by the device owner and the profile owner on the - * primary user and it applies globally - i.e. it disables airplane mode on the entire device. - * <p>The default value is <code>false</code>. + * <p> + * This restriction can only be set by the device owner, the profile owner on the primary user + * or the profile owner of an organization-owned managed profile on the parent profile, and it + * applies globally - i.e. it disables airplane mode on the entire device. + * <p> + * The default value is <code>false</code>. * * <p>Key for user restrictions. * <p>Type: Boolean @@ -729,9 +731,12 @@ public class UserManager { public static final String DISALLOW_APPS_CONTROL = "no_control_apps"; /** - * Specifies if a user is disallowed from mounting - * physical external media. This can only be set by device owners and profile owners on the - * primary user. The default value is <code>false</code>. + * Specifies if a user is disallowed from mounting physical external media. + * <p> + * This restriction can only be set by the device owner, the profile owner on the primary user + * or the profile owner of an organization-owned managed profile on the parent profile. + * <p> + * The default value is <code>false</code>. * * <p>Key for user restrictions. * <p>Type: Boolean @@ -743,10 +748,15 @@ public class UserManager { /** * Specifies if a user is disallowed from adjusting microphone volume. If set, the microphone - * will be muted. This can be set by device owners and profile owners. The default value is - * <code>false</code>. + * will be muted. + * <p> + * The default value is <code>false</code>. + * <p> + * Device owner and profile owner can set this restriction, although the restriction has no + * effect in a managed profile. When it is set by the profile owner of an organization-owned + * managed profile on the parent profile, it will disallow the personal user from adjusting the + * microphone volume. * - * <p>This restriction has no effect on managed profiles. * <p>Key for user restrictions. * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) @@ -773,10 +783,15 @@ public class UserManager { public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume"; /** - * Specifies that the user is not allowed to make outgoing - * phone calls. Emergency calls are still permitted. + * Specifies that the user is not allowed to make outgoing phone calls. Emergency calls are + * still permitted. + * <p> * The default value is <code>false</code>. - * <p>This restriction has no effect on managed profiles. + * <p> + * Device owner and profile owner can set this restriction, although the restriction has no + * effect in a managed profile. When it is set by the profile owner of an organization-owned + * managed profile on the parent profile, it will disallow the personal user from making + * outgoing phone calls. * * <p>Key for user restrictions. * <p>Type: Boolean diff --git a/core/java/android/os/VibrationEffect.aidl b/core/java/android/os/VibrationEffect.aidl index dcc79d798c3d..89478fac2f1a 100644 --- a/core/java/android/os/VibrationEffect.aidl +++ b/core/java/android/os/VibrationEffect.aidl @@ -17,6 +17,4 @@ package android.os; parcelable VibrationEffect; -parcelable VibrationEffect.OneShotVibration; -parcelable VibrationEffect.WaveformVibration; -parcelable VibrationEffect.EffectVibration; +parcelable VibrationEffect.Composition.PrimitiveEffect;
\ No newline at end of file diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 75b4724c7d26..2d218f4f2541 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -16,7 +16,9 @@ package android.os; +import android.annotation.FloatRange; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; @@ -28,9 +30,14 @@ import android.hardware.vibrator.V1_3.Effect; import android.net.Uri; import android.util.MathUtils; +import com.android.internal.util.Preconditions; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.Objects; /** * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}. @@ -41,6 +48,8 @@ public abstract class VibrationEffect implements Parcelable { private static final int PARCEL_TOKEN_ONE_SHOT = 1; private static final int PARCEL_TOKEN_WAVEFORM = 2; private static final int PARCEL_TOKEN_EFFECT = 3; + private static final int PARCEL_TOKEN_COMPOSITION = 4; + /** * The default vibration strength of the device. @@ -359,6 +368,16 @@ public abstract class VibrationEffect implements Parcelable { return null; } + /** + * Start composing a haptic effect. + * + * @see VibrationEffect.Composition + */ + @NonNull + public static VibrationEffect.Composition startComposition() { + return new VibrationEffect.Composition(); + } + @Override public int describeContents() { return 0; @@ -839,6 +858,331 @@ public abstract class VibrationEffect implements Parcelable { }; } + /** @hide */ + public static final class Composed extends VibrationEffect implements Parcelable { + private final ArrayList<Composition.PrimitiveEffect> mPrimitiveEffects; + + /** + * @hide + */ + @SuppressWarnings("unchecked") + public Composed(@NonNull Parcel in) { + this(in.readArrayList(Composed.class.getClassLoader())); + } + + /** + * @hide + */ + public Composed(List<Composition.PrimitiveEffect> effects) { + mPrimitiveEffects = new ArrayList<>(Objects.requireNonNull(effects)); + } + + /** + * @hide + */ + @NonNull + public List<Composition.PrimitiveEffect> getPrimitiveEffects() { + return mPrimitiveEffects; + } + + @Override + public long getDuration() { + return -1; + } + + + /** + * @hide + */ + @Override + public void validate() { + for (Composition.PrimitiveEffect effect : mPrimitiveEffects) { + Composition.checkPrimitive(effect.id); + Preconditions.checkArgumentInRange( + effect.scale, 0.0f, 1.0f, "scale"); + } + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_COMPOSITION); + out.writeList(mPrimitiveEffects); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Composed composed = (Composed) o; + return mPrimitiveEffects.equals(composed.mPrimitiveEffects); + } + + @Override + public int hashCode() { + return Objects.hash(mPrimitiveEffects); + } + + @Override + public String toString() { + return "Composed{mPrimitiveEffects=" + mPrimitiveEffects + '}'; + } + + public static final @NonNull Parcelable.Creator<Composed> CREATOR = + new Parcelable.Creator<Composed>() { + @Override + public Composed createFromParcel(@NonNull Parcel in) { + // Skip the type token + in.readInt(); + return new Composed(in); + } + + @Override + @NonNull + public Composed[] newArray(int size) { + return new Composed[size]; + } + }; + } + + /** + * A composition of haptic primitives that, when combined, create a single haptic effect. + * + * @see VibrationEffect#startComposition() + */ + public static class Composition { + /** @hide */ + @IntDef(prefix = { "PRIMITIVE_" }, value = { + PRIMITIVE_CLICK, + PRIMITIVE_THUD, + PRIMITIVE_SPIN, + PRIMITIVE_QUICK_RISE, + PRIMITIVE_SLOW_RISE, + PRIMITIVE_QUICK_FALL, + PRIMITIVE_LIGHT_TICK, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Primitive {} + + /** + * No haptic effect. Used to generate extended delays between primitives. + * @hide + */ + public static final int PRIMITIVE_NOOP = 0; + /** + * This effect should produce a sharp, crisp click sensation. + */ + public static final int PRIMITIVE_CLICK = 1; + /** + * A haptic effect that simulates downwards movement with gravity. Often + * followed by extra energy of hitting and reverberation to augment + * physicality. + */ + public static final int PRIMITIVE_THUD = 2; + /** + * A haptic effect that simulates spinning momentum. + */ + public static final int PRIMITIVE_SPIN = 3; + /** + * A haptic effect that simulates quick upward movement against gravity. + */ + public static final int PRIMITIVE_QUICK_RISE = 4; + /** + * A haptic effect that simulates slow upward movement against gravity. + */ + public static final int PRIMITIVE_SLOW_RISE = 5; + /** + * A haptic effect that simulates quick downwards movement with gravity. + */ + public static final int PRIMITIVE_QUICK_FALL = 6; + /** + * This very short effect should produce a light crisp sensation intended + * to be used repetitively for dynamic feedback. + */ + public static final int PRIMITIVE_LIGHT_TICK = 7; + + + private ArrayList<PrimitiveEffect> mEffects = new ArrayList<>(); + + /** + * Add a haptic primitive to the end of the current composition. + * + * Similar to {@link #addPrimitive(int, float, int)}, but with no delay and a + * default scale applied. + * + * @param primitiveId The primitive to add + * + * @return The {@link Composition} object to enable adding multiple primitives in one chain. + */ + @Nullable + public Composition addPrimitive(@Primitive int primitiveId) { + addPrimitive(primitiveId, /*scale*/ 1.0f, /*delay*/ 0); + return this; + } + + /** + * Add a haptic primitive to the end of the current composition. + * + * Similar to {@link #addPrimitive(int, float, int)}, but with no delay. + * + * @param primitiveId The primitive to add + * @param scale The scale to apply to the intensity of the primitive. + * + * @return The {@link Composition} object to enable adding multiple primitives in one chain. + */ + @Nullable + public Composition addPrimitive(@Primitive int primitiveId, + @FloatRange(from = 0f, to = 1f) float scale) { + addPrimitive(primitiveId, scale, /*delay*/ 0); + return this; + } + + /** + * Add a haptic primitive to the end of the current composition. + * + * @param primitiveId The primitive to add + * @param scale The scale to apply to the intensity of the primitive. + * @param delay The amount of time, in milliseconds, to wait before playing the prior + * primitive and this one + * @return The {@link Composition} object to enable adding multiple primitives in one chain. + */ + @Nullable + public Composition addPrimitive(@Primitive int primitiveId, + @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) { + mEffects.add(new PrimitiveEffect(checkPrimitive(primitiveId), scale, delay)); + return this; + } + + /** + * Compose all of the added primitives together into a single {@link VibrationEffect}. + * + * The {@link Composition} object is still valid after this call, so you can continue adding + * more primitives to it and generating more {@link VibrationEffect}s by calling this method + * again. + * + * @return The {@link VibrationEffect} resulting from the composition of the primitives. + */ + @NonNull + public VibrationEffect compose() { + if (mEffects.isEmpty()) { + throw new IllegalStateException( + "Composition must have at least one element to compose."); + } + return new VibrationEffect.Composed(mEffects); + } + + /** + * @throws IllegalArgumentException throws if the primitive ID is not within the valid range + * @hide + * + */ + static int checkPrimitive(int primitiveId) { + Preconditions.checkArgumentInRange(primitiveId, PRIMITIVE_NOOP, PRIMITIVE_LIGHT_TICK, + "primitiveId"); + return primitiveId; + } + + /** + * Convert the primitive ID to a human readable string for debugging + * @param id The ID to convert + * @return The ID in a human readable format. + * @hide + */ + public static String primitiveToString(@Primitive int id) { + switch (id) { + case PRIMITIVE_NOOP: + return "PRIMITIVE_NOOP"; + case PRIMITIVE_CLICK: + return "PRIMITIVE_CLICK"; + case PRIMITIVE_THUD: + return "PRIMITIVE_THUD"; + case PRIMITIVE_SPIN: + return "PRIMITIVE_SPIN"; + case PRIMITIVE_QUICK_RISE: + return "PRIMITIVE_QUICK_RISE"; + case PRIMITIVE_SLOW_RISE: + return "PRIMITIVE_SLOW_RISE"; + case PRIMITIVE_QUICK_FALL: + return "PRIMITIVE_QUICK_FALL"; + case PRIMITIVE_LIGHT_TICK: + return "PRIMITIVE_LIGHT_TICK"; + + default: + return Integer.toString(id); + + } + } + + + /** + * @hide + */ + public static class PrimitiveEffect implements Parcelable { + public int id; + public float scale; + public int delay; + + PrimitiveEffect(int id, float scale, int delay) { + this.id = id; + this.scale = scale; + this.delay = delay; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + dest.writeFloat(scale); + dest.writeInt(delay); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "PrimitiveEffect{" + + "id=" + primitiveToString(id) + + ", scale=" + scale + + ", delay=" + delay + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PrimitiveEffect that = (PrimitiveEffect) o; + return id == that.id + && Float.compare(that.scale, scale) == 0 + && delay == that.delay; + } + + @Override + public int hashCode() { + return Objects.hash(id, scale, delay); + } + + + public static final @NonNull Parcelable.Creator<PrimitiveEffect> CREATOR = + new Parcelable.Creator<PrimitiveEffect>() { + @Override + public PrimitiveEffect createFromParcel(Parcel in) { + return new PrimitiveEffect(in.readInt(), in.readFloat(), in.readInt()); + } + @Override + public PrimitiveEffect[] newArray(int size) { + return new PrimitiveEffect[size]; + } + }; + } + } + public static final @NonNull Parcelable.Creator<VibrationEffect> CREATOR = new Parcelable.Creator<VibrationEffect>() { @Override @@ -850,6 +1194,8 @@ public abstract class VibrationEffect implements Parcelable { return new Waveform(in); } else if (token == PARCEL_TOKEN_EFFECT) { return new Prebaked(in); + } else if (token == PARCEL_TOKEN_COMPOSITION) { + return new Composed(in); } else { throw new IllegalStateException( "Unexpected vibration event type token in parcel."); diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index ae75f3d0d7e6..f055c60e6a41 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; @@ -41,37 +42,42 @@ public abstract class Vibrator { /** * Vibration intensity: no vibrations. + * * @hide */ public static final int VIBRATION_INTENSITY_OFF = 0; /** * Vibration intensity: low. + * * @hide */ public static final int VIBRATION_INTENSITY_LOW = 1; /** * Vibration intensity: medium. + * * @hide */ public static final int VIBRATION_INTENSITY_MEDIUM = 2; /** * Vibration intensity: high. + * * @hide */ public static final int VIBRATION_INTENSITY_HIGH = 3; /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "VIBRATION_INTENSITY_" }, value = { - VIBRATION_INTENSITY_OFF, - VIBRATION_INTENSITY_LOW, - VIBRATION_INTENSITY_MEDIUM, - VIBRATION_INTENSITY_HIGH + @IntDef(prefix = {"VIBRATION_INTENSITY_"}, value = { + VIBRATION_INTENSITY_OFF, + VIBRATION_INTENSITY_LOW, + VIBRATION_INTENSITY_MEDIUM, + VIBRATION_INTENSITY_HIGH }) - public @interface VibrationIntensity{} + public @interface VibrationIntensity { + } private final String mPackageName; // The default vibration intensity level for haptic feedback. @@ -117,6 +123,7 @@ public abstract class Vibrator { /** * Get the default vibration intensity for haptic feedback. + * * @hide */ public int getDefaultHapticFeedbackIntensity() { @@ -125,13 +132,16 @@ public abstract class Vibrator { /** * Get the default vibration intensity for notifications. + * * @hide */ public int getDefaultNotificationVibrationIntensity() { return mDefaultNotificationVibrationIntensity; } - /** Get the default vibration intensity for ringtones. + /** + * Get the default vibration intensity for ringtones. + * * @hide */ public int getDefaultRingVibrationIntensity() { @@ -156,11 +166,12 @@ public abstract class Vibrator { * Configure an always-on haptics effect. * * @param alwaysOnId The board-specific always-on ID to configure. - * @param effect Vibration effect to assign to always-on id. Passing null will disable it. + * @param effect Vibration effect to assign to always-on id. Passing null will disable it. * @param attributes {@link AudioAttributes} corresponding to the vibration. For example, - * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or - * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for - * vibrations associated with incoming calls. May only be null when effect is null. + * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or + * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for + * vibrations associated with incoming calls. May only be null when effect is + * null. * @hide */ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON) @@ -183,7 +194,6 @@ public abstract class Vibrator { * Vibrate constantly for the specified period of time. * * @param milliseconds The number of milliseconds to vibrate. - * * @deprecated Use {@link #vibrate(VibrationEffect)} instead. */ @Deprecated @@ -196,11 +206,10 @@ public abstract class Vibrator { * Vibrate constantly for the specified period of time. * * @param milliseconds The number of milliseconds to vibrate. - * @param attributes {@link AudioAttributes} corresponding to the vibration. For example, - * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or - * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for - * vibrations associated with incoming calls. - * + * @param attributes {@link AudioAttributes} corresponding to the vibration. For example, + * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or + * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for + * vibrations associated with incoming calls. * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead. */ @Deprecated @@ -231,9 +240,8 @@ public abstract class Vibrator { * </p> * * @param pattern an array of longs of times for which to turn the vibrator on or off. - * @param repeat the index into pattern at which to repeat, or -1 if - * you don't want to repeat. - * + * @param repeat the index into pattern at which to repeat, or -1 if + * you don't want to repeat. * @deprecated Use {@link #vibrate(VibrationEffect)} instead. */ @Deprecated @@ -256,14 +264,13 @@ public abstract class Vibrator { * to start the repeat, or -1 to disable repeating. * </p> * - * @param pattern an array of longs of times for which to turn the vibrator on or off. - * @param repeat the index into pattern at which to repeat, or -1 if - * you don't want to repeat. + * @param pattern an array of longs of times for which to turn the vibrator on or off. + * @param repeat the index into pattern at which to repeat, or -1 if + * you don't want to repeat. * @param attributes {@link AudioAttributes} corresponding to the vibration. For example, - * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or - * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for - * vibrations associated with incoming calls. - * + * specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or + * {@link AudioAttributes#USAGE_NOTIFICATION_RINGTONE} for + * vibrations associated with incoming calls. * @deprecated Use {@link #vibrate(VibrationEffect, AudioAttributes)} instead. */ @Deprecated @@ -295,8 +302,9 @@ public abstract class Vibrator { } /** - * Like {@link #vibrate(int, String, VibrationEffect, AudioAttributes)}, but allows the + * Like {@link #vibrate(VibrationEffect, AudioAttributes)}, but allows the * caller to specify the vibration is owned by someone else and set reason for vibration. + * * @hide */ @RequiresPermission(android.Manifest.permission.VIBRATE) @@ -304,6 +312,85 @@ public abstract class Vibrator { String reason, AudioAttributes attributes); /** + * Query whether the vibrator supports the given effects. + * + * If the returned array is {@code null}, the hardware doesn't support querying its supported + * effects. It may support any or all effects, but there's no way to programmatically know + * whether a {@link #vibrate} call will be successful. + * + * If the returned array is non-null, then it will be the same length as the query array and + * the value at a given index will contain whether the effect at that same index in the + * querying array is supported or not. + * + * @param effectIds Which effects to query for. + * @return Whether the effects are supported. Null when the hardware doesn't tell us what it + * supports. + */ + @Nullable + public boolean[] areEffectsSupported( + @NonNull @VibrationEffect.EffectType int... effectIds) { + return new boolean[effectIds.length]; + } + + /** + * Query whether the vibrator supports all of the given effects. + * + * If the result is {@code null}, the hardware doesn't support querying its supported + * effects. It may support any or all effects, but there's no way to programmatically know + * whether a {@link #vibrate} call will be successful. + * + * If the returned array is non-null, then it will return whether all of the effects are + * supported by the hardware. + * + * @param effectIds Which effects to query for. + * @return Whether the effects are supported. {@code null} when the hardware doesn't tell us + * what it supports. + */ + @Nullable + public Boolean areAllEffectsSupported( + @NonNull @VibrationEffect.EffectType int... effectIds) { + for (boolean supported : areEffectsSupported(effectIds)) { + if (!supported) { + return false; + } + } + return true; + } + + + /** + * Query whether the vibrator supports the given primitives. + * + * The returned array will be the same length as the query array and the value at a given index + * will contain whether the effect at that same index in the querying array is supported or + * not. + * + * @param primitiveIds Which primitives to query for. + * @return Whether the primitives are supported. + */ + @NonNull + public boolean[] arePrimitivesSupported( + @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + return new boolean[primitiveIds.length]; + } + + /** + * Query whether the vibrator supports all of the given primitives. + * + * @param primitiveIds Which primitives to query for. + * @return Whether primitives effects are supported. + */ + public boolean areAllPrimitivesSupported( + @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + for (boolean supported : arePrimitivesSupported(primitiveIds)) { + if (!supported) { + return false; + } + } + return true; + } + + /** * Turn the vibrator off. */ @RequiresPermission(android.Manifest.permission.VIBRATE) diff --git a/core/java/android/os/incremental/IIncrementalManager.aidl b/core/java/android/os/incremental/IIncrementalManager.aidl index 17a310a5beb0..b415bc02fbcc 100644 --- a/core/java/android/os/incremental/IIncrementalManager.aidl +++ b/core/java/android/os/incremental/IIncrementalManager.aidl @@ -33,5 +33,7 @@ interface IIncrementalManager { boolean startDataLoader(int mountId); void showHealthBlockedUI(int mountId); void destroyDataLoader(int mountId); - void newFileForDataLoader(int mountId, long inode, in byte[] metadata); + + // fileId is a 16 byte long identifier. + void newFileForDataLoader(int mountId, in byte[] fileId, in byte[] metadata); } diff --git a/core/java/android/os/incremental/IIncrementalManagerNative.aidl b/core/java/android/os/incremental/IIncrementalManagerNative.aidl index 14215b1ea84d..2b6cd1478da8 100644 --- a/core/java/android/os/incremental/IIncrementalManagerNative.aidl +++ b/core/java/android/os/incremental/IIncrementalManagerNative.aidl @@ -17,6 +17,7 @@ package android.os.incremental; import android.content.pm.DataLoaderParamsParcel; +import android.os.incremental.IncrementalNewFileParams; /** @hide */ interface IIncrementalManagerNative { @@ -40,7 +41,7 @@ interface IIncrementalManagerNative { */ const int BIND_TEMPORARY = 0; const int BIND_PERMANENT = 1; - int makeBindMount(int storageId, in @utf8InCpp String pathUnderStorage, in @utf8InCpp String targetFullPath, int bindType); + int makeBindMount(int storageId, in @utf8InCpp String sourcePath, in @utf8InCpp String targetFullPath, int bindType); /** * Deletes an existing bind mount on a path under a storage. Returns 0 on success, and -errno on failure. @@ -48,49 +49,50 @@ interface IIncrementalManagerNative { int deleteBindMount(int storageId, in @utf8InCpp String targetFullPath); /** - * Creates a directory under a storage. The target directory is specified by its relative path under the storage. + * Creates a directory under a storage. The target directory is specified by its path. */ - int makeDirectory(int storageId, in @utf8InCpp String pathUnderStorage); + int makeDirectory(int storageId, in @utf8InCpp String path); /** - * Recursively creates a directory under a storage. The target directory is specified by its relative path under the storage. + * Recursively creates a directory under a storage. The target directory is specified by its path. * All the parent directories of the target directory will be created if they do not exist already. */ - int makeDirectories(int storageId, in @utf8InCpp String pathUnderStorage); + int makeDirectories(int storageId, in @utf8InCpp String path); /** - * Creates a file under a storage, specifying its name, size and metadata. + * Creates a file under a storage. */ - int makeFile(int storageId, in @utf8InCpp String pathUnderStorage, long size, in byte[] metadata); + int makeFile(int storageId, in @utf8InCpp String path, in IncrementalNewFileParams params); /** * Creates a file under a storage. Content of the file is from a range inside another file. - * Both files are specified by relative paths under storage. + * Both files are specified by their paths. */ - int makeFileFromRange(int storageId, in @utf8InCpp String targetPathUnderStorage, in @utf8InCpp String sourcePathUnderStorage, long start, long end); + int makeFileFromRange(int storageId, in @utf8InCpp String targetPath, in @utf8InCpp String sourcePath, long start, long end); /** * Creates a hard link between two files in two storage instances. - * Source and dest specified by parent storage IDs and their relative paths under the storage. + * Source and dest specified by parent storage IDs and their paths. * The source and dest storage instances should be in the same fs mount. * Note: destStorageId can be the same as sourceStorageId. */ - int makeLink(int sourceStorageId, in @utf8InCpp String sourcePathUnderStorage, int destStorageId, in @utf8InCpp String destPathUnderStorage); + int makeLink(int sourceStorageId, in @utf8InCpp String sourcePath, int destStorageId, in @utf8InCpp String destPath); /** - * Deletes a hard link in a storage, specified by the relative path of the link target under storage. + * Deletes a hard link in a storage, specified by its path. */ - int unlink(int storageId, in @utf8InCpp String pathUnderStorage); + int unlink(int storageId, in @utf8InCpp String path); /** - * Checks if a file's certain range is loaded. File is specified by relative file path under storage. + * Checks if a file's certain range is loaded. File is specified by its path. */ - boolean isFileRangeLoaded(int storageId, in @utf8InCpp String pathUnderStorage, long start, long end); + boolean isFileRangeLoaded(int storageId, in @utf8InCpp String path, long start, long end); /** - * Reads the metadata of a file. File is specified by relative path under storage. + * Reads the metadata of a file. File is specified by either its path or 16 byte id. */ - byte[] getFileMetadata(int storageId, in @utf8InCpp String pathUnderStorage); + byte[] getMetadataByPath(int storageId, in @utf8InCpp String path); + byte[] getMetadataById(int storageId, in byte[] fileId); /** * Starts loading data for a storage. diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java index 63335a08f003..a0bfc1bf56b8 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -134,8 +134,8 @@ public final class IncrementalFileStorages { } if (!new File(mDefaultDir, apk.getName()).exists()) { - mDefaultStorage.makeFile(apk.getName(), apk.getSize(), - apk.getMetadata()); + mDefaultStorage.makeFile(apk.getName(), apk.getSize(), null, + apk.getMetadata(), 0, null, null, null); } // Assuming APK files are already named properly, e.g., "base.apk" mDefaultStorage.makeLink(apk.getName(), mApkStorage, apk.getName()); @@ -167,7 +167,8 @@ public final class IncrementalFileStorages { current += '/'; } String libFilePath = current + Paths.get(lib.getName()).getFileName(); - mDefaultStorage.makeFile(libFilePath, lib.getSize(), lib.getMetadata()); + mDefaultStorage.makeFile(libFilePath, lib.getSize(), null, lib.getMetadata(), 0, null, null, + null); mDefaultStorage.makeLink(libFilePath, mApkStorage, libFilePath); } @@ -183,7 +184,8 @@ public final class IncrementalFileStorages { IncrementalManager.CREATE_MODE_CREATE | IncrementalManager.CREATE_MODE_TEMPORARY_BIND); } - mDefaultStorage.makeFile(obb.getName(), obb.getSize(), obb.getMetadata()); + mDefaultStorage.makeFile(obb.getName(), obb.getSize(), null, obb.getMetadata(), 0, null, + null, null); mDefaultStorage.makeLink(obb.getName(), mObbStorage, obb.getName()); } diff --git a/core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl b/core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl index 0ae353d2741f..6018ad1efc4a 100644 --- a/core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl +++ b/core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl @@ -17,11 +17,12 @@ package android.os.incremental; /** - * Wraps two file descriptors that Incremental Service uses to communicate + * Wraps the file descriptors Incremental Service uses to communicate * with Incremental FileSystem. * @hide */ parcelable IncrementalFileSystemControlParcel { - @nullable ParcelFileDescriptor cmd; - @nullable ParcelFileDescriptor log; + ParcelFileDescriptor cmd; + ParcelFileDescriptor pendingReads; + ParcelFileDescriptor log; } diff --git a/core/java/android/os/incremental/IncrementalNewFileParams.aidl b/core/java/android/os/incremental/IncrementalNewFileParams.aidl new file mode 100644 index 000000000000..182732cebdf1 --- /dev/null +++ b/core/java/android/os/incremental/IncrementalNewFileParams.aidl @@ -0,0 +1,31 @@ +/* + * 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.incremental; + +import android.os.incremental.IncrementalSignature; + +/** + * All the parameters to create a new file on IncFS + * FileId is a 16 byte-long identifier. + * @hide + */ +parcelable IncrementalNewFileParams { + long size; + byte[] fileId; + byte[] metadata; + @nullable IncrementalSignature signature; +} diff --git a/core/java/android/os/incremental/IncrementalSignature.aidl b/core/java/android/os/incremental/IncrementalSignature.aidl new file mode 100644 index 000000000000..729e8e5556a8 --- /dev/null +++ b/core/java/android/os/incremental/IncrementalSignature.aidl @@ -0,0 +1,31 @@ +/* + * 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.incremental; + +/** {@hide} */ +parcelable IncrementalSignature { + /* + * Stable AIDL doesn't support constants, but here's the possible values + * const int HASH_ALGO_NONE = 0; + * const int HASH_ALGO_SHA256 = 1; + */ + + int hashAlgorithm = 0; + byte[] rootHash; + byte[] additionalData; + byte[] signature; +} diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index 275086832c51..91dda0899a63 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -22,6 +22,8 @@ import android.os.RemoteException; import java.io.File; import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.UUID; /** * Provides operations on an Incremental File System directory, using IncrementalServiceNative. @@ -64,14 +66,14 @@ public final class IncrementalStorage { * Temporarily bind-mounts a subdir under the current storage directory to a target directory. * The bind-mount will NOT be preserved between device reboots. * - * @param sourcePathUnderStorage Source path as a relative path under current storage - * directory. - * @param targetPath Absolute path to the target directory. + * @param sourcePath Source path as a relative path under current storage + * directory. + * @param targetPath Absolute path to the target directory. */ - public void bind(@NonNull String sourcePathUnderStorage, @NonNull String targetPath) + public void bind(@NonNull String sourcePath, @NonNull String targetPath) throws IOException { try { - int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath, + int res = mService.makeBindMount(mId, sourcePath, targetPath, IIncrementalManagerNative.BIND_TEMPORARY); if (res < 0) { throw new IOException("bind() failed with errno " + -res); @@ -96,13 +98,13 @@ public final class IncrementalStorage { * Permanently bind-mounts a subdir under the current storage directory to a target directory. * The bind-mount WILL be preserved between device reboots. * - * @param sourcePathUnderStorage Relative path under the current storage directory. - * @param targetPath Absolute path to the target directory. + * @param sourcePath Relative path under the current storage directory. + * @param targetPath Absolute path to the target directory. */ - public void bindPermanent(@NonNull String sourcePathUnderStorage, @NonNull String targetPath) + public void bindPermanent(@NonNull String sourcePath, @NonNull String targetPath) throws IOException { try { - int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath, + int res = mService.makeBindMount(mId, sourcePath, targetPath, IIncrementalManagerNative.BIND_PERMANENT); if (res < 0) { throw new IOException("bind() permanent failed with errno " + -res); @@ -131,11 +133,11 @@ public final class IncrementalStorage { /** * Creates a sub-directory under the current storage directory. * - * @param pathUnderStorage Relative path of the sub-directory, e.g., "subdir" + * @param path Relative path of the sub-directory, e.g., "subdir" */ - public void makeDirectory(@NonNull String pathUnderStorage) throws IOException { + public void makeDirectory(@NonNull String path) throws IOException { try { - int res = mService.makeDirectory(mId, pathUnderStorage); + int res = mService.makeDirectory(mId, path); if (res < 0) { throw new IOException("makeDirectory() failed with errno " + -res); } @@ -148,11 +150,11 @@ public final class IncrementalStorage { * Creates a sub-directory under the current storage directory. If its parent dirs do not exist, * create the parent dirs as well. * - * @param pathUnderStorage Relative path of the sub-directory, e.g., "subdir/subsubdir" + * @param path Full path. */ - public void makeDirectories(@NonNull String pathUnderStorage) throws IOException { + public void makeDirectories(@NonNull String path) throws IOException { try { - int res = mService.makeDirectories(mId, pathUnderStorage); + int res = mService.makeDirectories(mId, path); if (res < 0) { throw new IOException("makeDirectory() failed with errno " + -res); } @@ -164,15 +166,27 @@ public final class IncrementalStorage { /** * Creates a file under the current storage directory. * - * @param pathUnderStorage Relative path of the new file. + * @param path Relative path of the new file. * @param size Size of the new file in bytes. * @param metadata Metadata bytes. */ - public void makeFile(@NonNull String pathUnderStorage, long size, - @Nullable byte[] metadata) throws IOException { + public void makeFile(@NonNull String path, long size, @Nullable UUID id, + @Nullable byte[] metadata, int hashAlgorithm, @Nullable byte[] rootHash, + @Nullable byte[] additionalData, @Nullable byte[] signature) throws IOException { try { - int res = mService.makeFile(mId, pathUnderStorage, size, metadata); - if (res < 0) { + final IncrementalNewFileParams params = new IncrementalNewFileParams(); + params.size = size; + params.metadata = metadata; + params.fileId = idToBytes(id); + if (hashAlgorithm != 0 || signature != null) { + params.signature = new IncrementalSignature(); + params.signature.hashAlgorithm = hashAlgorithm; + params.signature.rootHash = rootHash; + params.signature.additionalData = additionalData; + params.signature.signature = signature; + } + int res = mService.makeFile(mId, path, params); + if (res != 0) { throw new IOException("makeFile() failed with errno " + -res); } } catch (RemoteException e) { @@ -184,15 +198,15 @@ public final class IncrementalStorage { * Creates a file in Incremental storage. The content of the file is mapped from a range inside * a source file in the same storage. * - * @param destRelativePath Target relative path under storage. - * @param sourceRelativePath Source relative path under storage. + * @param destPath Target full path. + * @param sourcePath Source full path. * @param rangeStart Starting offset (in bytes) in the source file. * @param rangeEnd Ending offset (in bytes) in the source file. */ - public void makeFileFromRange(@NonNull String destRelativePath, - @NonNull String sourceRelativePath, long rangeStart, long rangeEnd) throws IOException { + public void makeFileFromRange(@NonNull String destPath, + @NonNull String sourcePath, long rangeStart, long rangeEnd) throws IOException { try { - int res = mService.makeFileFromRange(mId, destRelativePath, sourceRelativePath, + int res = mService.makeFileFromRange(mId, destPath, sourcePath, rangeStart, rangeEnd); if (res < 0) { throw new IOException("makeFileFromRange() failed, errno " + -res); @@ -206,15 +220,15 @@ public final class IncrementalStorage { * Creates a hard-link between two paths, which can be under different storages but in the same * Incremental File System. * - * @param sourcePathUnderStorage The relative path of the source. - * @param destStorage The target storage of the link target. - * @param destPathUnderStorage The relative path of the target. + * @param sourcePath The absolute path of the source. + * @param destStorage The target storage of the link target. + * @param destPath The absolute path of the target. */ - public void makeLink(@NonNull String sourcePathUnderStorage, IncrementalStorage destStorage, - @NonNull String destPathUnderStorage) throws IOException { + public void makeLink(@NonNull String sourcePath, IncrementalStorage destStorage, + @NonNull String destPath) throws IOException { try { - int res = mService.makeLink(mId, sourcePathUnderStorage, destStorage.getId(), - destPathUnderStorage); + int res = mService.makeLink(mId, sourcePath, destStorage.getId(), + destPath); if (res < 0) { throw new IOException("makeLink() failed with errno " + -res); } @@ -226,11 +240,11 @@ public final class IncrementalStorage { /** * Deletes a hard-link under the current storage directory. * - * @param pathUnderStorage The relative path of the target. + * @param path The absolute path of the target. */ - public void unlink(@NonNull String pathUnderStorage) throws IOException { + public void unlink(@NonNull String path) throws IOException { try { - int res = mService.unlink(mId, pathUnderStorage); + int res = mService.unlink(mId, path); if (res < 0) { throw new IOException("unlink() failed with errno " + -res); } @@ -242,13 +256,14 @@ public final class IncrementalStorage { /** * Rename an old file name to a new file name under the current storage directory. * - * @param sourcePathUnderStorage Old file path as a relative path to the storage directory. - * @param destPathUnderStorage New file path as a relative path to the storage directory. + * @param sourcepath Old file path as a full path to the storage directory. + * @param destpath New file path as a full path to the storage directory. */ - public void moveFile(@NonNull String sourcePathUnderStorage, - @NonNull String destPathUnderStorage) throws IOException { + public void moveFile(@NonNull String sourcepath, + @NonNull String destpath) throws IOException { + //TODO(zyy): implement using rename(2) when confirmed that IncFS supports it. try { - int res = mService.makeLink(mId, sourcePathUnderStorage, mId, destPathUnderStorage); + int res = mService.makeLink(mId, sourcepath, mId, destpath); if (res < 0) { throw new IOException("moveFile() failed at makeLink(), errno " + -res); } @@ -256,7 +271,7 @@ public final class IncrementalStorage { e.rethrowFromSystemServer(); } try { - mService.unlink(mId, sourcePathUnderStorage); + mService.unlink(mId, sourcepath); } catch (RemoteException ignored) { } } @@ -274,7 +289,7 @@ public final class IncrementalStorage { throw new IOException("moveDir() requires that destination dir already exists."); } try { - int res = mService.makeBindMount(mId, "", destPath, + int res = mService.makeBindMount(mId, sourcePath, destPath, IIncrementalManagerNative.BIND_PERMANENT); if (res < 0) { throw new IOException("moveDir() failed at making bind mount, errno " + -res); @@ -291,24 +306,24 @@ public final class IncrementalStorage { /** * Checks whether a file under the current storage directory is fully loaded. * - * @param pathUnderStorage The relative path of the file. + * @param path The relative path of the file. * @return True if the file is fully loaded. */ - public boolean isFileFullyLoaded(@NonNull String pathUnderStorage) { - return isFileRangeLoaded(pathUnderStorage, 0, -1); + public boolean isFileFullyLoaded(@NonNull String path) { + return isFileRangeLoaded(path, 0, -1); } /** * Checks whether a range in a file if loaded. * - * @param pathUnderStorage The relative path of the file. + * @param path The relative path of the file. * @param start The starting offset of the range. * @param end The ending offset of the range. * @return True if the file is fully loaded. */ - public boolean isFileRangeLoaded(@NonNull String pathUnderStorage, long start, long end) { + public boolean isFileRangeLoaded(@NonNull String path, long start, long end) { try { - return mService.isFileRangeLoaded(mId, pathUnderStorage, start, end); + return mService.isFileRangeLoaded(mId, path, start, end); } catch (RemoteException e) { e.rethrowFromSystemServer(); return false; @@ -318,13 +333,65 @@ public final class IncrementalStorage { /** * Returns the metadata object of an IncFs File. * - * @param pathUnderStorage The relative path of the file. + * @param path The relative path of the file. + * @return Byte array that contains metadata bytes. + */ + @Nullable + public byte[] getFileMetadata(@NonNull String path) { + try { + return mService.getMetadataByPath(mId, path); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + private static final int UUID_BYTE_SIZE = 16; + + /** + * Converts UUID to a byte array usable for Incremental API calls + * + * @param id The id to convert + * @return Byte array that contains the same ID. + */ + public static byte[] idToBytes(UUID id) { + if (id == null) { + return null; + } + final ByteBuffer buf = ByteBuffer.wrap(new byte[UUID_BYTE_SIZE]); + buf.putLong(id.getMostSignificantBits()); + buf.putLong(id.getLeastSignificantBits()); + return buf.array(); + } + + /** + * Converts UUID from a byte array usable for Incremental API calls + * + * @param bytes The id in byte array format, 16 bytes long + * @return UUID constructed from the byte array. + */ + public static UUID bytesToId(byte[] bytes) { + if (bytes.length != UUID_BYTE_SIZE) { + throw new IllegalArgumentException("Expected array of size " + UUID_BYTE_SIZE + + ", got " + bytes.length); + } + final ByteBuffer buf = ByteBuffer.wrap(bytes); + long msb = buf.getLong(); + long lsb = buf.getLong(); + return new UUID(msb, lsb); + } + + /** + * Returns the metadata object of an IncFs File. + * + * @param id The file id. * @return Byte array that contains metadata bytes. */ @Nullable - public byte[] getFileMetadata(@NonNull String pathUnderStorage) { + public byte[] getFileMetadata(@NonNull UUID id) { try { - return mService.getFileMetadata(mId, pathUnderStorage); + final byte[] rawId = idToBytes(id); + return mService.getMetadataById(mId, rawId); } catch (RemoteException e) { e.rethrowFromSystemServer(); return null; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a62412068fda..9666c12699e8 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2721,6 +2721,7 @@ public final class Settings { } } }); + currentGeneration = generation; } } if (mGenerationTracker != null && currentGeneration == @@ -2801,14 +2802,7 @@ public final class Settings { } mValues.clear(); } else { - boolean prefixCached = false; - int size = mValues.size(); - for (int i = 0; i < size; ++i) { - if (mValues.keyAt(i).startsWith(prefix)) { - prefixCached = true; - break; - } - } + boolean prefixCached = mValues.containsKey(prefix); if (prefixCached) { if (!names.isEmpty()) { for (String name : names) { @@ -2817,9 +2811,11 @@ public final class Settings { } } } else { - for (int i = 0; i < size; ++i) { + for (int i = 0; i < mValues.size(); ++i) { String key = mValues.keyAt(i); - if (key.startsWith(prefix)) { + // Explicitly exclude the prefix as it is only there to + // signal that the prefix has been cached. + if (key.startsWith(prefix) && !key.equals(prefix)) { keyValues.put(key, mValues.get(key)); } } @@ -2907,12 +2903,15 @@ public final class Settings { } } }); + currentGeneration = generation; } } if (mGenerationTracker != null && currentGeneration == mGenerationTracker.getCurrentGeneration()) { // cache the complete list of flags for the namespace mValues.putAll(flagsToValues); + // Adding the prefix as a signal that the prefix is cached. + mValues.put(prefix, null); } } return keyValues; @@ -5216,7 +5215,6 @@ public final class Settings { MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NUM_OPEN_NETWORKS_KEPT); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ON); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_P2P_DEVICE_NAME); - MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SAVED_STATE); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ENHANCED_AUTO_JOIN); @@ -10324,16 +10322,6 @@ public final class Settings { public static final String BLE_SCAN_BACKGROUND_MODE = "ble_scan_background_mode"; /** - * Used to save the Wifi_ON state prior to tethering. - * This state will be checked to restore Wifi after - * the user turns off tethering. - * - * @hide - */ - @UnsupportedAppUsage - public static final String WIFI_SAVED_STATE = "wifi_saved_state"; - - /** * The interval in milliseconds to scan as used by the wifi supplicant * @hide */ @@ -14104,7 +14092,7 @@ public final class Settings { * @hide */ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) - static Map<String, String> getStrings(@NonNull ContentResolver resolver, + public static Map<String, String> getStrings(@NonNull ContentResolver resolver, @NonNull String namespace, @NonNull List<String> names) { List<String> compositeNames = new ArrayList<>(names.size()); for (String name : names) { @@ -14163,8 +14151,9 @@ public final class Settings { * @hide */ @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) - static boolean setStrings(@NonNull ContentResolver resolver, @NonNull String namespace, - @NonNull Map<String, String> keyValues) throws DeviceConfig.BadConfigException { + public static boolean setStrings(@NonNull ContentResolver resolver, + @NonNull String namespace, @NonNull Map<String, String> keyValues) + throws DeviceConfig.BadConfigException { HashMap<String, String> compositeKeyValueMap = new HashMap<>(keyValues.keySet().size()); for (Map.Entry<String, String> entry : keyValues.entrySet()) { compositeKeyValueMap.put( @@ -14241,6 +14230,12 @@ public final class Settings { } } + /** @hide */ + public static void clearProviderForTest() { + sProviderHolder.clearProviderForTest(); + sNameValueCache.clearGenerationTrackerForTest(); + } + private static String createCompositeName(@NonNull String namespace, @NonNull String name) { Preconditions.checkNotNull(namespace); Preconditions.checkNotNull(name); diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 9da584ea1487..b6f5138a6582 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -1296,17 +1296,6 @@ public final class Telephony { "android.provider.action.EXTERNAL_PROVIDER_CHANGE"; /** - * Same as {@link #ACTION_DEFAULT_SMS_PACKAGE_CHANGED} but it's implicit (e.g. sent to - * all apps) and requires - * {@link android.Manifest.permission#MONITOR_DEFAULT_SMS_PACKAGE} to receive. - * - * @hide - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL = - "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL"; - - /** * Broadcast action: When SMS-MMS db is being created. If file-based encryption is * supported, this broadcast indicates creation of the db in credential-encrypted * storage. A boolean is specified in {@link #EXTRA_IS_INITIAL_CREATE} to indicate if @@ -4038,15 +4027,6 @@ public final class Telephony { @Retention(RetentionPolicy.SOURCE) public @interface EditStatus {} - /** @hide */ - @IntDef({ - SKIP_464XLAT_DEFAULT, - SKIP_464XLAT_DISABLE, - SKIP_464XLAT_ENABLE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Skip464XlatStatus {} - /** * Compat framework change ID for the APN db read permission change. * @@ -4297,6 +4277,15 @@ public final class Telephony { public static final String LANGUAGE_CODE = "language"; /** + * Dats coding scheme of the message. + * <p> + * The data coding scheme (dcs) value defined in 3GPP TS 23.038 section 4 + * </p> + * <P>Type: INTEGER</P> + */ + public static final String DATA_CODING_SCHEME = "dcs"; + + /** * Message body. * <P>Type: TEXT</P> */ @@ -4384,18 +4373,32 @@ public final class Telephony { public static final String DEFAULT_SORT_ORDER = DELIVERY_TIME + " DESC"; /** - * The timestamp in millisecond of when the device received the message. + * The timestamp in millisecond, reported by {@link System#currentTimeMillis()}, when the + * device received the message. * <P>Type: BIGINT</P> */ public static final String RECEIVED_TIME = "received_time"; /** + * The timestamp in millisecond, reported by {@link System#currentTimeMillis()}, when + * location was checked last time. Note this is only applicable to geo-targeting message. + * For non geo-targeting message. the field will be set to -1. + * <P>Type: BIGINT</P> + */ + public static final String LOCATION_CHECK_TIME = "location_check_time"; + /** * Indicates that whether the message has been broadcasted to the application. * <P>Type: BOOLEAN</P> */ public static final String MESSAGE_BROADCASTED = "message_broadcasted"; /** + * Indicates that whether the message has been displayed to the user. + * <P>Type: BOOLEAN</P> + */ + public static final String MESSAGE_DISPLAYED = "message_displayed"; + + /** * The Warning Area Coordinates Elements. This element is used for geo-fencing purpose. * * The geometry and its coordinates are separated vertical bar, the first item is the diff --git a/core/java/android/security/net/config/WfaCertificateSource.java b/core/java/android/security/net/config/WfaCertificateSource.java index f212ef8bf447..545e4b0926b1 100644 --- a/core/java/android/security/net/config/WfaCertificateSource.java +++ b/core/java/android/security/net/config/WfaCertificateSource.java @@ -23,12 +23,15 @@ import java.io.File; * @hide */ public final class WfaCertificateSource extends DirectoryCertificateSource { + private static final String CACERTS_WFA_PATH = + "/apex/com.android.wifi/etc/security/cacerts_wfa"; + private static class NoPreloadHolder { private static final WfaCertificateSource INSTANCE = new WfaCertificateSource(); } private WfaCertificateSource() { - super(new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts_wfa")); + super(new File(CACERTS_WFA_PATH)); } public static WfaCertificateSource getInstance() { diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 9333dbd1e1d5..36f3a7899700 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -748,8 +748,8 @@ public final class FillResponse implements Parcelable { parcel.writeParcelableArray(mFieldClassificationIds, flags); parcel.writeInt(mFlags); parcel.writeIntArray(mCancelIds); - parcel.writeInt(mRequestId); parcel.writeParcelable(mInlineActions, flags); + parcel.writeInt(mRequestId); } public static final @android.annotation.NonNull Parcelable.Creator<FillResponse> CREATOR = diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java index 79c21521fe2d..40c0ac00a5a9 100644 --- a/core/java/android/service/quicksettings/Tile.java +++ b/core/java/android/service/quicksettings/Tile.java @@ -65,6 +65,7 @@ public final class Tile implements Parcelable { private CharSequence mLabel; private CharSequence mSubtitle; private CharSequence mContentDescription; + private CharSequence mStateDescription; // Default to inactive until clients of the new API can update. private int mState = STATE_INACTIVE; @@ -177,6 +178,14 @@ public final class Tile implements Parcelable { } /** + * Gets the current state description for the tile. + */ + @Nullable + public CharSequence getStateDescription() { + return mStateDescription; + } + + /** * Sets the current content description for the tile. * * Does not take effect until {@link #updateTile()} is called. @@ -187,6 +196,17 @@ public final class Tile implements Parcelable { this.mContentDescription = contentDescription; } + /** + * Sets the current state description for the tile. + * + * Does not take effect until {@link #updateTile()} is called. + * + * @param stateDescription New state description to use. + */ + public void setStateDescription(@Nullable CharSequence stateDescription) { + this.mStateDescription = stateDescription; + } + @Override public int describeContents() { return 0; @@ -215,6 +235,7 @@ public final class Tile implements Parcelable { TextUtils.writeToParcel(mLabel, dest, flags); TextUtils.writeToParcel(mSubtitle, dest, flags); TextUtils.writeToParcel(mContentDescription, dest, flags); + TextUtils.writeToParcel(mStateDescription, dest, flags); } private void readFromParcel(Parcel source) { @@ -227,6 +248,7 @@ public final class Tile implements Parcelable { mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + mStateDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); } public static final @android.annotation.NonNull Creator<Tile> CREATOR = new Creator<Tile>() { diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index dd78c78654c3..e9285cc7931d 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -223,6 +223,9 @@ public abstract class WallpaperService extends Service { SurfaceControl mSurfaceControl = new SurfaceControl(); + // Unused relayout out-param + SurfaceControl mTmpSurfaceControl = new SurfaceControl(); + final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { { mRequestedFormat = PixelFormat.RGBX_8888; @@ -902,7 +905,7 @@ public abstract class WallpaperService extends Service { View.VISIBLE, 0, -1, mWinFrame, mContentInsets, mVisibleInsets, mStableInsets, mBackdropFrame, mDisplayCutout, mMergedConfiguration, mSurfaceControl, - mInsetsState, mSurfaceSize); + mInsetsState, mSurfaceSize, mTmpSurfaceControl); if (mSurfaceControl.isValid()) { mSurfaceHolder.mSurface.copyFrom(mSurfaceControl); mSurfaceControl.release(); diff --git a/core/java/android/telephony/CellBroadcastIntents.java b/core/java/android/telephony/CellBroadcastIntents.java index 2e0810835a52..32d330e1a47f 100644 --- a/core/java/android/telephony/CellBroadcastIntents.java +++ b/core/java/android/telephony/CellBroadcastIntents.java @@ -19,6 +19,8 @@ package android.telephony; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.app.AppOpsManager; import android.content.BroadcastReceiver; @@ -43,6 +45,14 @@ public class CellBroadcastIntents { private static final String EXTRA_MESSAGE = "message"; /** + * Broadcast intent action for notifying area information has been updated. The information + * can be retrieved by {@link CellBroadcastService#getCellBroadcastAreaInfo(int)} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_AREA_INFO_UPDATED = + "android.telephony.action.AREA_INFO_UPDATED"; + + /** * @hide */ private CellBroadcastIntents() { diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 6787c46c046e..4024db1e16c4 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -20,8 +20,12 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.compat.Compatibility; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.Context; import android.os.Binder; +import android.os.Build; import android.os.RemoteException; import android.os.ServiceManager; import android.telephony.Annotation.ApnType; @@ -199,6 +203,13 @@ public class TelephonyRegistryManager { } /** + * To check the SDK version for {@link #listenForSubscriber}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P) + private static final long LISTEN_CODE_CHANGE = 147600208L; + + /** * Listen for incoming subscriptions * @param subId Subscription ID * @param pkg Package name @@ -210,6 +221,16 @@ public class TelephonyRegistryManager { public void listenForSubscriber(int subId, @NonNull String pkg, @NonNull String featureId, @NonNull PhoneStateListener listener, int events, boolean notifyNow) { try { + // subId from PhoneStateListener is deprecated Q on forward, use the subId from + // TelephonyManager instance. Keep using subId from PhoneStateListener for pre-Q. + if (Compatibility.isChangeEnabled(LISTEN_CODE_CHANGE)) { + // Since mSubId in PhoneStateListener is deprecated from Q on forward, this is + // the only place to set mSubId and its for "informational" only. + listener.mSubId = (events == PhoneStateListener.LISTEN_NONE) + ? SubscriptionManager.INVALID_SUBSCRIPTION_ID : subId; + } else if (listener.mSubId != null) { + subId = listener.mSubId; + } sRegistry.listenForSubscriber( subId, pkg, featureId, listener.callback, events, notifyNow); } catch (RemoteException e) { diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index 3b6ec0b95802..49cebc141636 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -18,7 +18,7 @@ package android.text.format; import android.util.TimeFormatException; -import libcore.timezone.ZoneInfoDB; +import libcore.timezone.ZoneInfoDb; import libcore.util.ZoneInfo; import java.io.IOException; @@ -1118,9 +1118,9 @@ public class Time { private static ZoneInfo lookupZoneInfo(String timezoneId) { try { - ZoneInfo zoneInfo = ZoneInfoDB.getInstance().makeTimeZone(timezoneId); + ZoneInfo zoneInfo = ZoneInfoDb.getInstance().makeTimeZone(timezoneId); if (zoneInfo == null) { - zoneInfo = ZoneInfoDB.getInstance().makeTimeZone("GMT"); + zoneInfo = ZoneInfoDb.getInstance().makeTimeZone("GMT"); } if (zoneInfo == null) { throw new AssertionError("GMT not found: \"" + timezoneId + "\""); diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java index eebccf4aa577..8a5864e92db1 100644 --- a/core/java/android/timezone/TelephonyLookup.java +++ b/core/java/android/timezone/TelephonyLookup.java @@ -30,9 +30,9 @@ import java.util.Objects; * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) -public class TelephonyLookup { +public final class TelephonyLookup { - private static Object sLock = new Object(); + private static final Object sLock = new Object(); @GuardedBy("sLock") private static TelephonyLookup sInstance; diff --git a/core/java/android/timezone/TelephonyNetwork.java b/core/java/android/timezone/TelephonyNetwork.java index ae39fbddfa1c..487b3f2f143b 100644 --- a/core/java/android/timezone/TelephonyNetwork.java +++ b/core/java/android/timezone/TelephonyNetwork.java @@ -27,7 +27,7 @@ import java.util.Objects; * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) -public class TelephonyNetwork { +public final class TelephonyNetwork { @NonNull private final libcore.timezone.TelephonyNetwork mDelegate; diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java index 079d0882f191..2ddd3d998d8e 100644 --- a/core/java/android/timezone/TelephonyNetworkFinder.java +++ b/core/java/android/timezone/TelephonyNetworkFinder.java @@ -28,7 +28,7 @@ import java.util.Objects; * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) -public class TelephonyNetworkFinder { +public final class TelephonyNetworkFinder { @NonNull private final libcore.timezone.TelephonyNetworkFinder mDelegate; diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java index 9327b001a9c8..c76bb1d1fd28 100644 --- a/core/java/android/timezone/TimeZoneFinder.java +++ b/core/java/android/timezone/TimeZoneFinder.java @@ -32,7 +32,7 @@ import java.util.Objects; @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class TimeZoneFinder { - private static Object sLock = new Object(); + private static final Object sLock = new Object(); @GuardedBy("sLock") private static TimeZoneFinder sInstance; diff --git a/core/java/android/timezone/TzDataSetVersion.java b/core/java/android/timezone/TzDataSetVersion.java index aba7c4c15903..605155e76c2c 100644 --- a/core/java/android/timezone/TzDataSetVersion.java +++ b/core/java/android/timezone/TzDataSetVersion.java @@ -89,7 +89,7 @@ public final class TzDataSetVersion { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) - public static class TzDataSetException extends Exception { + public static final class TzDataSetException extends Exception { /** Creates an instance with a message. */ public TzDataSetException(String message) { diff --git a/core/java/android/timezone/ZoneInfoDb.java b/core/java/android/timezone/ZoneInfoDb.java index eb191e8e3272..4612a56df117 100644 --- a/core/java/android/timezone/ZoneInfoDb.java +++ b/core/java/android/timezone/ZoneInfoDb.java @@ -32,7 +32,7 @@ import java.util.Objects; @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class ZoneInfoDb { - private static Object sLock = new Object(); + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ZoneInfoDb sInstance; @@ -43,16 +43,16 @@ public final class ZoneInfoDb { public static ZoneInfoDb getInstance() { synchronized (sLock) { if (sInstance == null) { - sInstance = new ZoneInfoDb(libcore.timezone.ZoneInfoDB.getInstance()); + sInstance = new ZoneInfoDb(libcore.timezone.ZoneInfoDb.getInstance()); } } return sInstance; } @NonNull - private final libcore.timezone.ZoneInfoDB mDelegate; + private final libcore.timezone.ZoneInfoDb mDelegate; - private ZoneInfoDb(libcore.timezone.ZoneInfoDB delegate) { + private ZoneInfoDb(libcore.timezone.ZoneInfoDb delegate) { mDelegate = Objects.requireNonNull(delegate); } diff --git a/core/java/android/util/CloseGuard.java b/core/java/android/util/CloseGuard.java index 6ac769623bff..ba504a3b4167 100644 --- a/core/java/android/util/CloseGuard.java +++ b/core/java/android/util/CloseGuard.java @@ -26,7 +26,7 @@ import android.annotation.NonNull; * A simple example: <pre> {@code * class Foo { * - * private final CloseGuard guard = CloseGuard.get(); + * private final CloseGuard guard = new CloseGuard(); * * ... * @@ -64,7 +64,7 @@ import android.annotation.NonNull; * be deferred. For example: <pre> {@code * class Bar { * - * private final CloseGuard guard = CloseGuard.get(); + * private final CloseGuard guard = new CloseGuard(); * * ... * diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 37dd781d4468..c325874405a5 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -26,7 +26,7 @@ import android.os.SystemClock; import libcore.timezone.CountryTimeZones; import libcore.timezone.CountryTimeZones.TimeZoneMapping; import libcore.timezone.TimeZoneFinder; -import libcore.timezone.ZoneInfoDB; +import libcore.timezone.ZoneInfoDb; import java.io.PrintWriter; import java.text.SimpleDateFormat; @@ -134,7 +134,7 @@ public class TimeUtils { * during the lifetime of an activity. */ public static String getTimeZoneDatabaseVersion() { - return ZoneInfoDB.getInstance().getVersion(); + return ZoneInfoDb.getInstance().getVersion(); } /** @hide Field length that can hold 999 days of time */ diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 4d71136d0af1..19793b945ffd 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -231,6 +231,7 @@ public class GestureDetector { private int mTouchSlopSquare; private int mDoubleTapTouchSlopSquare; private int mDoubleTapSlopSquare; + private float mAmbiguousGestureMultiplier; @UnsupportedAppUsage private int mMinimumFlingVelocity; private int mMaximumFlingVelocity; @@ -452,6 +453,7 @@ public class GestureDetector { //noinspection deprecation mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); + mAmbiguousGestureMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier(); } else { final ViewConfiguration configuration = ViewConfiguration.get(context); touchSlop = configuration.getScaledTouchSlop(); @@ -459,6 +461,7 @@ public class GestureDetector { doubleTapSlop = configuration.getScaledDoubleTapSlop(); mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity(); + mAmbiguousGestureMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); } mTouchSlopSquare = touchSlop * touchSlop; mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop; @@ -661,7 +664,6 @@ public class GestureDetector { hasPendingLongPress && ambiguousGesture; if (shouldInhibitDefaultAction) { // Inhibit default long press - final float multiplier = ViewConfiguration.getAmbiguousGestureMultiplier(); if (distance > slopSquare) { // The default action here is to remove long press. But if the touch // slop below gets increased, and we never exceed the modified touch @@ -675,13 +677,14 @@ public class GestureDetector { LONG_PRESS, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS, 0 /* arg2 */), - ev.getDownTime() + (long) (longPressTimeout * multiplier)); + ev.getDownTime() + + (long) (longPressTimeout * mAmbiguousGestureMultiplier)); } // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll // until the gesture is resolved. // However, for safety, simply increase the touch slop in case the // classification is erroneous. Since the value is squared, multiply twice. - slopSquare *= multiplier * multiplier; + slopSquare *= mAmbiguousGestureMultiplier * mAmbiguousGestureMultiplier; } if (distance > slopSquare) { diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl index 00edb3a29e87..cb82f168d991 100644 --- a/core/java/android/view/IPinnedStackController.aidl +++ b/core/java/android/view/IPinnedStackController.aidl @@ -53,9 +53,4 @@ interface IPinnedStackController { * {@param bounds} here is the final destination bounds. */ void resetBoundsAnimation(in Rect bounds); - - /** - * Reports the current default and movement bounds to controller. - */ - void reportBounds(in Rect defaultBounds, in Rect movementBounds); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index e3446e1f7b57..1677357dedbe 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -92,6 +92,9 @@ interface IWindowSession { * @param outSurface Object in which is placed the new display surface. * @param insetsState The current insets state in the system. * @param outSurfaceSize The width and height of the surface control + * @param outBlastSurfaceControl A BLAST SurfaceControl allocated by the WindowManager + * the SurfaceControl willl be managed by the client side, but the WindowManager + * may use it as a deferTransaction barrier. * * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS}, * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}. @@ -103,7 +106,8 @@ interface IWindowSession { out Rect outBackdropFrame, out DisplayCutout.ParcelableWrapper displayCutout, out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl, - out InsetsState insetsState, out Point outSurfaceSize); + out InsetsState insetsState, out Point outSurfaceSize, + out SurfaceControl outBlastSurfaceControl); /* * Notify the window manager that an application is relaunching and diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index d0694375df53..2e377518f6e0 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -87,7 +87,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}. */ @Override - @ShowResult int requestShow(boolean fromIme) { + public @ShowResult int requestShow(boolean fromIme) { // TODO: ResultReceiver for IME. // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag. if (fromIme) { @@ -95,7 +95,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { } return getImm().requestImeShow(null /* resultReceiver */) - ? ShowResult.SHOW_DELAYED : ShowResult.SHOW_FAILED; + ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED; } /** diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 27edb0b69bdd..0645c98e994a 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -52,12 +52,6 @@ public interface InsetsAnimationControlCallbacks { void notifyFinished(InsetsAnimationControlImpl controller, boolean shown); /** - * Get the description of the insets state. - * @return {@link InsetsState} for adjusting corresponding {@link InsetsSource}. - */ - InsetsState getState(); - - /** * Apply the new params to the surface. * @param params The {@link android.view.SyncRtSurfaceTransactionApplier.SurfaceParams} to * apply. diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 405eccd56e3c..e863aa06ed26 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -191,13 +191,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll if (mCancelled) { return; } - InsetsState state = new InsetsState(mController.getState()); - for (int i = mControls.size() - 1; i >= 0; i--) { - InsetsSourceControl control = mControls.valueAt(i); - state.getSource(control.getType()).setVisible(shown); - } - Insets insets = getInsetsFromState(state, mFrame, null /* typeSideMap */); - setInsetsAndAlpha(insets, 1f /* alpha */, 1f /* fraction */); + setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */); mFinished = true; mShownOnFinish = shown; } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index c6e383539a82..6f76497572d7 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -19,6 +19,7 @@ package android.view; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.toPublicType; import static android.view.WindowInsets.Type.all; +import static android.view.WindowInsets.Type.ime; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED; @@ -31,6 +32,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; +import android.os.Handler; import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; @@ -55,6 +57,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.function.BiFunction; /** * Implements {@link WindowInsetsController} on the client. @@ -64,6 +67,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private static final int ANIMATION_DURATION_SHOW_MS = 275; private static final int ANIMATION_DURATION_HIDE_MS = 340; + private static final int PENDING_CONTROL_TIMEOUT_MS = 2000; static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); @@ -235,12 +239,36 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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. + */ + private static class PendingControlRequest { + + PendingControlRequest(@InsetsType int types, WindowInsetsAnimationControlListener listener, + long durationMs, Interpolator interpolator, @AnimationType int animationType, + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { + this.types = types; + this.listener = listener; + this.durationMs = durationMs; + this.interpolator = interpolator; + this.animationType = animationType; + this.layoutInsetsDuringAnimation = layoutInsetsDuringAnimation; + } + + final @InsetsType int types; + final WindowInsetsAnimationControlListener listener; + final long durationMs; + final Interpolator interpolator; + final @AnimationType int animationType; + final @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation; + } private final String TAG = "InsetsControllerImpl"; @@ -248,8 +276,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final InsetsState mTmpState = new InsetsState(); private final Rect mFrame = new Rect(); + private final BiFunction<InsetsController, Integer, InsetsSourceConsumer> mConsumerCreator; private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>(); private final ViewRootImpl mViewRoot; + private final Handler mHandler; private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>(); private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>(); @@ -263,7 +293,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final Rect mLastLegacyContentInsets = new Rect(); private final Rect mLastLegacyStableInsets = new Rect(); - private int mPendingTypesToShow; + /** Pending control request that is waiting on IME to be ready to be shown */ + private PendingControlRequest mPendingImeControlRequest; private int mLastLegacySoftInputMode; private int mLastLegacySystemUiFlags; @@ -271,8 +302,26 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private SyncRtSurfaceTransactionApplier mApplier; + private Runnable mPendingControlTimeout = this::abortPendingImeControlRequest; + public InsetsController(ViewRootImpl viewRoot) { + this(viewRoot, (controller, type) -> { + if (type == ITYPE_IME) { + return new ImeInsetsSourceConsumer(controller.mState, Transaction::new, controller); + } else { + return new InsetsSourceConsumer(type, controller.mState, Transaction::new, + controller); + } + }, viewRoot.mHandler); + } + + @VisibleForTesting + public InsetsController(ViewRootImpl viewRoot, + BiFunction<InsetsController, Integer, InsetsSourceConsumer> consumerCreator, + Handler handler) { mViewRoot = viewRoot; + mConsumerCreator = consumerCreator; + mHandler = handler; mAnimCallback = () -> { mAnimCallbackScheduled = false; if (mRunningAnimations.isEmpty()) { @@ -393,7 +442,21 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation show(types, false /* fromIme */); } - void show(@InsetsType int types, boolean fromIme) { + @VisibleForTesting + public void show(@InsetsType int types, boolean fromIme) { + + // Handle pending request ready in case there was one set. + if (fromIme && mPendingImeControlRequest != null) { + PendingControlRequest pendingRequest = mPendingImeControlRequest; + mPendingImeControlRequest = null; + mHandler.removeCallbacks(mPendingControlTimeout); + controlAnimationUnchecked(pendingRequest.types, pendingRequest.listener, mFrame, + true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator, + false /* fade */, pendingRequest.animationType, + pendingRequest.layoutInsetsDuringAnimation); + return; + } + // TODO: Support a ResultReceiver for IME. // TODO(b/123718661): Make show() work for multi-session IME. int typesReady = 0; @@ -463,6 +526,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { if (types == 0) { // nothing to animate. + listener.onCancelled(); return; } cancelExistingControllers(types); @@ -471,19 +535,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final SparseArray<InsetsSourceControl> controls = new SparseArray<>(); Pair<Integer, Boolean> typesReadyPair = collectSourceControls( - fromIme, internalTypes, controls, listener); + fromIme, internalTypes, controls); int typesReady = typesReadyPair.first; - boolean isReady = typesReadyPair.second; - if (!isReady) { - // IME isn't ready, all requested types would be shown once IME is ready. - mPendingTypesToShow = typesReady; - // TODO: listener for pending types. + boolean imeReady = typesReadyPair.second; + if (!imeReady) { + // IME isn't ready, all requested types will be animated once IME is ready + abortPendingImeControlRequest(); + mPendingImeControlRequest = new PendingControlRequest(types, listener, durationMs, + interpolator, animationType, layoutInsetsDuringAnimation); + mHandler.postDelayed(mPendingControlTimeout, PENDING_CONTROL_TIMEOUT_MS); return; } - // pending types from previous request. - typesReady = collectPendingTypes(typesReady); - if (typesReady == 0) { listener.onCancelled(); return; @@ -496,13 +559,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** - * @return Pair of (types ready to animate, is ready to animate). + * @return Pair of (types ready to animate, IME ready to animate). */ private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, - ArraySet<Integer> internalTypes, SparseArray<InsetsSourceControl> controls, - WindowInsetsAnimationControlListener listener) { + ArraySet<Integer> internalTypes, SparseArray<InsetsSourceControl> controls) { int typesReady = 0; - boolean isReady = true; + boolean imeReady = true; for (int i = internalTypes.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); boolean setVisible = !consumer.isRequestedVisible(); @@ -512,16 +574,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation case ShowResult.SHOW_IMMEDIATELY: typesReady |= InsetsState.toPublicType(consumer.getType()); break; - case ShowResult.SHOW_DELAYED: - isReady = false; + case ShowResult.IME_SHOW_DELAYED: + imeReady = false; break; - case ShowResult.SHOW_FAILED: + case ShowResult.IME_SHOW_FAILED: // IME cannot be shown (since it didn't have focus), proceed // with animation of other types. - if (mPendingTypesToShow != 0) { - // remove IME from pending because view no longer has focus. - mPendingTypesToShow &= ~InsetsState.toPublicType(ITYPE_IME); - } break; } } else { @@ -538,13 +596,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation controls.put(consumer.getType(), control); } } - return new Pair<>(typesReady, isReady); - } - - private int collectPendingTypes(@InsetsType int typesReady) { - typesReady |= mPendingTypesToShow; - mPendingTypesToShow = 0; - return typesReady; + return new Pair<>(typesReady, imeReady); } private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode( @@ -577,6 +629,17 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation cancelAnimation(control, true /* invokeCallback */); } } + if ((types & ime()) != 0) { + abortPendingImeControlRequest(); + } + } + + private void abortPendingImeControlRequest() { + if (mPendingImeControlRequest != null) { + mPendingImeControlRequest.listener.onCancelled(); + mPendingImeControlRequest = null; + mHandler.removeCallbacks(mPendingControlTimeout); + } } @VisibleForTesting @@ -608,6 +671,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation cancelAnimation(control, true /* invokeCallback */); } } + if (consumer.getType() == ITYPE_IME) { + abortPendingImeControlRequest(); + } } private void cancelAnimation(InsetsAnimationControlImpl control, boolean invokeCallback) { @@ -635,7 +701,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (controller != null) { return controller; } - controller = createConsumerOfType(type); + controller = mConsumerCreator.apply(this, type); mSourceConsumers.put(type, controller); return controller; } @@ -696,14 +762,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return ANIMATION_TYPE_NONE; } - private InsetsSourceConsumer createConsumerOfType(int type) { - if (type == ITYPE_IME) { - return new ImeInsetsSourceConsumer(mState, Transaction::new, this); - } else { - return new InsetsSourceConsumer(type, mState, Transaction::new, this); - } - } - /** * Sends the local visibility state back to window manager. */ diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 35a82b8bfd54..ddfd38c0320b 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -36,7 +36,7 @@ import java.util.function.Supplier; public class InsetsSourceConsumer { @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.SHOW_DELAYED, ShowResult.SHOW_FAILED}) + @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.IME_SHOW_DELAYED, ShowResult.IME_SHOW_FAILED}) @interface ShowResult { /** * Window type is ready to be shown, will be shown immidiately. @@ -46,12 +46,12 @@ public class InsetsSourceConsumer { * Result will be delayed. Window needs to be prepared or request is not from controller. * Request will be delegated to controller and may or may not be shown. */ - int SHOW_DELAYED = 1; + int IME_SHOW_DELAYED = 1; /** * Window will not be shown because one of the conditions couldn't be met. * (e.g. in IME's case, when no editor is focused.) */ - int SHOW_FAILED = 2; + int IME_SHOW_FAILED = 2; } protected final InsetsController mController; @@ -155,7 +155,8 @@ public class InsetsSourceConsumer { * {@link android.inputmethodservice.InputMethodService}). * @return @see {@link ShowResult}. */ - @ShowResult int requestShow(boolean fromController) { + @VisibleForTesting + public @ShowResult int requestShow(boolean fromController) { return ShowResult.SHOW_IMMEDIATELY; } diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index c638717b13c0..c91096e5aa25 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -19,6 +19,7 @@ package android.view; import static android.view.Display.INVALID_DISPLAY; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -1269,6 +1270,7 @@ public class KeyEvent extends InputEvent implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private int mSource; private int mDisplayId; + private @Nullable byte[] mHmac; @UnsupportedAppUsage private int mMetaState; @UnsupportedAppUsage @@ -1546,6 +1548,7 @@ public class KeyEvent extends InputEvent implements Parcelable { mDeviceId = origEvent.mDeviceId; mSource = origEvent.mSource; mDisplayId = origEvent.mDisplayId; + mHmac = origEvent.mHmac == null ? null : origEvent.mHmac.clone(); mScanCode = origEvent.mScanCode; mFlags = origEvent.mFlags; mCharacters = origEvent.mCharacters; @@ -1573,6 +1576,7 @@ public class KeyEvent extends InputEvent implements Parcelable { mDeviceId = origEvent.mDeviceId; mSource = origEvent.mSource; mDisplayId = origEvent.mDisplayId; + mHmac = null; // Don't copy HMAC, it will be invalid because eventTime is changing mScanCode = origEvent.mScanCode; mFlags = origEvent.mFlags; mCharacters = origEvent.mCharacters; @@ -1600,7 +1604,8 @@ public class KeyEvent extends InputEvent implements Parcelable { */ public static KeyEvent obtain(long downTime, long eventTime, int action, int code, int repeat, int metaState, - int deviceId, int scancode, int flags, int source, int displayId, String characters) { + int deviceId, int scancode, int flags, int source, int displayId, @Nullable byte[] hmac, + String characters) { KeyEvent ev = obtain(); ev.mDownTime = downTime; ev.mEventTime = eventTime; @@ -1613,6 +1618,7 @@ public class KeyEvent extends InputEvent implements Parcelable { ev.mFlags = flags; ev.mSource = source; ev.mDisplayId = displayId; + ev.mHmac = hmac; ev.mCharacters = characters; return ev; } @@ -1627,7 +1633,7 @@ public class KeyEvent extends InputEvent implements Parcelable { int code, int repeat, int metaState, int deviceId, int scancode, int flags, int source, String characters) { return obtain(downTime, eventTime, action, code, repeat, metaState, deviceId, scancode, - flags, source, INVALID_DISPLAY, characters); + flags, source, INVALID_DISPLAY, null /* hmac */, characters); } /** @@ -1650,6 +1656,7 @@ public class KeyEvent extends InputEvent implements Parcelable { ev.mFlags = other.mFlags; ev.mSource = other.mSource; ev.mDisplayId = other.mDisplayId; + ev.mHmac = other.mHmac == null ? null : other.mHmac.clone(); ev.mCharacters = other.mCharacters; return ev; } @@ -1738,6 +1745,7 @@ public class KeyEvent extends InputEvent implements Parcelable { mDeviceId = origEvent.mDeviceId; mSource = origEvent.mSource; mDisplayId = origEvent.mDisplayId; + mHmac = null; // Don't copy the hmac, it will be invalid since action is changing mScanCode = origEvent.mScanCode; mFlags = origEvent.mFlags; // Don't copy mCharacters, since one way or the other we'll lose it @@ -3091,6 +3099,7 @@ public class KeyEvent extends InputEvent implements Parcelable { mDeviceId = in.readInt(); mSource = in.readInt(); mDisplayId = in.readInt(); + mHmac = in.createByteArray(); mAction = in.readInt(); mKeyCode = in.readInt(); mRepeatCount = in.readInt(); @@ -3109,6 +3118,7 @@ public class KeyEvent extends InputEvent implements Parcelable { out.writeInt(mDeviceId); out.writeInt(mSource); out.writeInt(mDisplayId); + out.writeByteArray(mHmac); out.writeInt(mAction); out.writeInt(mKeyCode); out.writeInt(mRepeatCount); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c5f4faf2f462..d70bf22fcfbf 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -22,10 +22,6 @@ import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LO import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; -import static android.view.WindowInsets.Type.ime; -import static android.view.WindowInsets.Type.systemBars; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static java.lang.Math.max; @@ -146,7 +142,6 @@ import android.widget.FrameLayout; import android.widget.ScrollBarDrawable; import com.android.internal.R; -import com.android.internal.policy.DecorView; import com.android.internal.view.TooltipPopup; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.widget.ScrollBarUtils; @@ -4911,6 +4906,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int mTouchSlop; /** + * Cache the ambiguous gesture multiplier from the context that created the view. + */ + private float mAmbiguousGestureMultiplier; + + /** * Object that handles automatic animation of view properties. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) @@ -5224,7 +5224,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) | (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) | (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT); - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + + final ViewConfiguration configuration = ViewConfiguration.get(context); + mTouchSlop = configuration.getScaledTouchSlop(); + mAmbiguousGestureMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); + setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); mUserPaddingStart = UNDEFINED_PADDING; mUserPaddingEnd = UNDEFINED_PADDING; @@ -15642,15 +15646,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; int touchSlop = mTouchSlop; if (ambiguousGesture && hasPendingLongPressCallback()) { - final float ambiguousMultiplier = - ViewConfiguration.getAmbiguousGestureMultiplier(); if (!pointInView(x, y, touchSlop)) { // The default action here is to cancel long press. But instead, we // just extend the timeout here, in case the classification // stays ambiguous. removeLongPressCallback(); long delay = (long) (ViewConfiguration.getLongPressTimeout() - * ambiguousMultiplier); + * mAmbiguousGestureMultiplier); // Subtract the time already spent delay -= event.getEventTime() - event.getDownTime(); checkForLongClick( @@ -15659,7 +15661,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } - touchSlop *= ambiguousMultiplier; + touchSlop *= mAmbiguousGestureMultiplier; } // Be lenient about moving outside of buttons @@ -21856,7 +21858,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!concatMatrix && (parentFlags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS | ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN && - canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) && + canvas.quickReject(mLeft, mTop, mRight, mBottom) && (mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) { mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED; return more; @@ -22140,6 +22142,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) + * 7. If necessary, draw the default focus highlight */ // Step 1, draw the background, if needed @@ -22346,6 +22349,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); + // Step 7, draw the default focus highlight + drawDefaultFocusHighlight(canvas); + if (isShowingLayoutBounds()) { debugDrawFocus(canvas); } @@ -23241,11 +23247,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Draw the default focus highlight onto the canvas. + * Draw the default focus highlight onto the canvas if there is one and this view is focused. * @param canvas the canvas where we're drawing the highlight. */ private void drawDefaultFocusHighlight(Canvas canvas) { - if (mDefaultFocusHighlight != null) { + if (mDefaultFocusHighlight != null && isFocused()) { if (mDefaultFocusHighlightSizeChanged) { mDefaultFocusHighlightSizeChanged = false; final int l = mScrollX; diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 774a2dea6311..a66b508cf523 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -29,6 +29,7 @@ import android.os.RemoteException; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.SparseArray; +import android.util.TypedValue; /** * Contains methods to standard constants used in the UI for timeouts, sizes, and distances. @@ -313,6 +314,7 @@ public class ViewConfiguration { private final int mPagingTouchSlop; private final int mDoubleTapSlop; private final int mWindowTouchSlop; + private final float mAmbiguousGestureMultiplier; private final int mMaximumDrawingCacheSize; private final int mOverscrollDistance; private final int mOverflingDistance; @@ -351,6 +353,7 @@ public class ViewConfiguration { mPagingTouchSlop = PAGING_TOUCH_SLOP; mDoubleTapSlop = DOUBLE_TAP_SLOP; mWindowTouchSlop = WINDOW_TOUCH_SLOP; + mAmbiguousGestureMultiplier = AMBIGUOUS_GESTURE_MULTIPLIER; //noinspection deprecation mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE; mOverscrollDistance = OVERSCROLL_DISTANCE; @@ -397,6 +400,13 @@ public class ViewConfiguration { mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f); mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f); + final TypedValue multiplierValue = new TypedValue(); + res.getValue( + com.android.internal.R.dimen.config_ambiguousGestureMultiplier, + multiplierValue, + true /*resolveRefs*/); + mAmbiguousGestureMultiplier = multiplierValue.getFloat(); + // Size of the screen in bytes, in ARGB_8888 format final WindowManager win = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); final Display display = win.getDefaultDisplay(); @@ -951,22 +961,35 @@ public class ViewConfiguration { } /** + * The multiplication factor for inhibiting default gestures. + * * If a MotionEvent has {@link android.view.MotionEvent#CLASSIFICATION_AMBIGUOUS_GESTURE} set, - * then certain actions, such as scrolling, will be inhibited. - * However, to account for the possibility of incorrect classification, - * the default scrolling will only be inhibited if the pointer travels less than - * (getScaledTouchSlop() * this factor). - * Likewise, the default long press timeout will be increased by this factor for some situations - * where the default behaviour is to cancel it. + * then certain actions, such as scrolling, will be inhibited. However, to account for the + * possibility of an incorrect classification, existing gesture thresholds (e.g. scrolling + * touch slop and the long-press timeout) should be scaled by this factor and remain in effect. * - * @return The multiplication factor for inhibiting default gestures. + * @deprecated Use {@link #getScaledAmbiguousGestureMultiplier()}. */ + @Deprecated @FloatRange(from = 1.0) public static float getAmbiguousGestureMultiplier() { return AMBIGUOUS_GESTURE_MULTIPLIER; } /** + * The multiplication factor for inhibiting default gestures. + * + * If a MotionEvent has {@link android.view.MotionEvent#CLASSIFICATION_AMBIGUOUS_GESTURE} set, + * then certain actions, such as scrolling, will be inhibited. However, to account for the + * possibility of an incorrect classification, existing gesture thresholds (e.g. scrolling + * touch slop and the long-press timeout) should be scaled by this factor and remain in effect. + */ + @FloatRange(from = 1.0) + public float getScaledAmbiguousGestureMultiplier() { + return mAmbiguousGestureMultiplier; + } + + /** * Report if the device has a permanent menu key available to the user. * * <p>As of Android 3.0, devices may not have a permanent menu key available. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7a93dcc9e4ec..841c43fb1392 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -507,7 +507,7 @@ public final class ViewRootImpl implements ViewParent, @UnsupportedAppUsage public final Surface mSurface = new Surface(); private final SurfaceControl mSurfaceControl = new SurfaceControl(); - private SurfaceControl mBlastSurfaceControl; + private SurfaceControl mBlastSurfaceControl = new SurfaceControl(); private BLASTBufferQueue mBlastBufferQueue; @@ -636,6 +636,7 @@ public final class ViewRootImpl implements ViewParent, InputEventConsistencyVerifier.isInstrumentationEnabled() ? new InputEventConsistencyVerifier(this, 0) : null; + private final InsetsController mInsetsController; private final ImeFocusController mImeFocusController; /** @@ -646,7 +647,6 @@ public final class ViewRootImpl implements ViewParent, return mImeFocusController; } - private final InsetsController mInsetsController = new InsetsController(this); private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); @@ -705,6 +705,7 @@ public final class ViewRootImpl implements ViewParent, mFallbackEventHandler = new PhoneFallbackEventHandler(context); mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); + mInsetsController = new InsetsController(this); String processorOverrideName = context.getResources().getString( R.string.config_inputEventCompatProcessorOverrideClassName); @@ -1690,23 +1691,17 @@ public final class ViewRootImpl implements ViewParent, .build(); setBoundsLayerCrop(); mTransaction.show(mBoundsLayer).apply(); - } - return mBoundsLayer; + } + return mBoundsLayer; } Surface getOrCreateBLASTSurface(int width, int height) { if (mSurfaceControl == null || !mSurfaceControl.isValid()) { return null; } - if (mBlastSurfaceControl == null) { - mBlastSurfaceControl = new SurfaceControl.Builder(mSurfaceSession) - .setParent(mSurfaceControl) - .setName("BLAST") - .setBLASTLayer() - .build(); + if ((mBlastBufferQueue != null) && mBlastSurfaceControl.isValid()) { mBlastBufferQueue = new BLASTBufferQueue( mBlastSurfaceControl, width, height); - } mBlastBufferQueue.update(mBlastSurfaceControl, width, height); @@ -3683,32 +3678,29 @@ public final class ViewRootImpl implements ViewParent, Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); boolean usingAsyncReport = false; + boolean reportNextDraw = mReportNextDraw; // Capture the original value if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { ArrayList<Runnable> commitCallbacks = mAttachInfo.mTreeObserver .captureFrameCommitCallbacks(); - if (mReportNextDraw) { - usingAsyncReport = true; + final boolean needFrameCompleteCallback = mNextDrawUseBLASTSyncTransaction || + (commitCallbacks != null && commitCallbacks.size() > 0) || + mReportNextDraw; + usingAsyncReport = mReportNextDraw; + if (needFrameCompleteCallback) { final Handler handler = mAttachInfo.mHandler; mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> handler.postAtFrontOfQueue(() -> { finishBLASTSync(); - // TODO: Use the frame number - pendingDrawFinished(); + if (reportNextDraw) { + // TODO: Use the frame number + pendingDrawFinished(); + } if (commitCallbacks != null) { for (int i = 0; i < commitCallbacks.size(); i++) { commitCallbacks.get(i).run(); } } })); - } else if (commitCallbacks != null && commitCallbacks.size() > 0) { - final Handler handler = mAttachInfo.mHandler; - mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> - handler.postAtFrontOfQueue(() -> { - finishBLASTSync(); - for (int i = 0; i < commitCallbacks.size(); i++) { - commitCallbacks.get(i).run(); - } - })); } } @@ -7344,7 +7336,8 @@ public final class ViewRootImpl implements ViewParent, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mTmpFrame, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingBackDropFrame, mPendingDisplayCutout, - mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize); + mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize, + mBlastSurfaceControl); if (mSurfaceControl.isValid()) { if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { mSurface.copyFrom(mSurfaceControl); diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 9f2784889cab..91778aaf51fd 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -161,7 +161,7 @@ public class WindowlessWindowManager implements IWindowSession { Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize) { + Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { State state = null; synchronized (this) { state = mStateForWindow.get(window.asBinder()); diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java index 80e17b5e8217..c2d4596e17dc 100644 --- a/core/java/android/widget/ScrollBarDrawable.java +++ b/core/java/android/widget/ScrollBarDrawable.java @@ -138,7 +138,7 @@ public class ScrollBarDrawable extends Drawable implements Drawable.Callback { } final Rect r = getBounds(); - if (canvas.quickReject(r.left, r.top, r.right, r.bottom, Canvas.EdgeType.AA)) { + if (canvas.quickReject(r.left, r.top, r.right, r.bottom)) { return; } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index a2736333383e..21067127a8e7 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -210,4 +210,14 @@ oneway interface IStatusBar * Cancels toast with token {@code token} in {@code packageName}. */ void hideToast(String packageName, IBinder token); + + /** + * Notifies SystemUI to start tracing. + */ + void startTracing(); + + /** + * Notifies SystemUI to stop tracing. + */ + void stopTracing(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index a9f7b8455807..5ce83c2db44d 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -78,6 +78,7 @@ interface IStatusBarService in int notificationLocation, boolean modifiedBeforeSending); void onNotificationSettingsViewed(String key); void onNotificationBubbleChanged(String key, boolean isBubble); + void onBubbleNotificationSuppressionChanged(String key, boolean isSuppressed); void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName); void clearInlineReplyUriPermissions(String key); @@ -123,4 +124,19 @@ interface IStatusBarService * Dismiss the warning that the device is about to go to sleep due to user inactivity. */ void dismissInattentiveSleepWarning(boolean animated); + + /** + * Notifies SystemUI to start tracing. + */ + void startTracing(); + + /** + * Notifies SystemUI to stop tracing. + */ + void stopTracing(); + + /** + * Returns whether SystemUI tracing is enabled. + */ + boolean isTracing(); } diff --git a/services/core/java/com/android/server/utils/TraceBuffer.java b/core/java/com/android/internal/util/TraceBuffer.java index 0567960e05e4..fe8a59e160bd 100644 --- a/services/core/java/com/android/server/utils/TraceBuffer.java +++ b/core/java/com/android/internal/util/TraceBuffer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.utils; +package com.android.internal.util; import android.util.proto.ProtoOutputStream; @@ -27,19 +27,87 @@ import java.io.OutputStream; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Queue; +import java.util.function.Consumer; /** * Buffer used for tracing and logging. + * + * @param <P> The class type of the proto provider + * @param <S> The proto class type of the encapsulating proto + * @param <T> The proto class type of the individual entry protos in the buffer + * + * {@hide} */ -public class TraceBuffer { +public class TraceBuffer<P, S extends P, T extends P> { private final Object mBufferLock = new Object(); - private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>(); + private final ProtoProvider<P, S, T> mProtoProvider; + private final Queue<T> mBuffer = new ArrayDeque<>(); + private final Consumer mProtoDequeuedCallback; private int mBufferUsedSize; private int mBufferCapacity; + /** + * An interface to get protos from different sources (ie. fw-proto/proto-lite/nano-proto) for + * the trace buffer. + * + * @param <P> The class type of the proto provider + * @param <S> The proto class type of the encapsulating proto + * @param <T> The proto class type of the individual protos in the buffer + */ + public interface ProtoProvider<P, S extends P, T extends P> { + /** + * @return The size of the given proto. + */ + int getItemSize(P proto); + + /** + * @return The bytes of the given proto. + */ + byte[] getBytes(P proto); + + /** + * Writes the given encapsulating proto and buffer of protos to the given output + * stream. + */ + void write(S encapsulatingProto, Queue<T> buffer, OutputStream os) throws IOException; + } + + /** + * An implementation of the ProtoProvider that uses only the framework ProtoOutputStream. + */ + private static class ProtoOutputStreamProvider implements + ProtoProvider<ProtoOutputStream, ProtoOutputStream, ProtoOutputStream> { + @Override + public int getItemSize(ProtoOutputStream proto) { + return proto.getRawSize(); + } + + @Override + public byte[] getBytes(ProtoOutputStream proto) { + return proto.getBytes(); + } + + @Override + public void write(ProtoOutputStream encapsulatingProto, Queue<ProtoOutputStream> buffer, + OutputStream os) throws IOException { + os.write(encapsulatingProto.getBytes()); + for (ProtoOutputStream protoOutputStream : buffer) { + byte[] protoBytes = protoOutputStream.getBytes(); + os.write(protoBytes); + } + } + } + public TraceBuffer(int bufferCapacity) { + this(bufferCapacity, new ProtoOutputStreamProvider(), null); + } + + public TraceBuffer(int bufferCapacity, ProtoProvider protoProvider, + Consumer<T> protoDequeuedCallback) { mBufferCapacity = bufferCapacity; + mProtoProvider = protoProvider; + mProtoDequeuedCallback = protoDequeuedCallback; resetBuffer(); } @@ -65,8 +133,8 @@ public class TraceBuffer { * @throws IllegalStateException if the element cannot be added because it is larger * than the buffer size. */ - public void add(ProtoOutputStream proto) { - int protoLength = proto.getRawSize(); + public void add(T proto) { + int protoLength = mProtoProvider.getItemSize(proto); if (protoLength > mBufferCapacity) { throw new IllegalStateException("Trace object too large for the buffer. Buffer size:" + mBufferCapacity + " Object size: " + protoLength); @@ -79,26 +147,22 @@ public class TraceBuffer { } } - boolean contains(byte[] other) { + @VisibleForTesting + public boolean contains(byte[] other) { return mBuffer.stream() - .anyMatch(p -> Arrays.equals(p.getBytes(), other)); + .anyMatch(p -> Arrays.equals(mProtoProvider.getBytes(p), other)); } /** - * Writes the trace buffer to disk inside the encapsulatingProto.. + * Writes the trace buffer to disk inside the encapsulatingProto. */ - public void writeTraceToFile(File traceFile, ProtoOutputStream encapsulatingProto) + public void writeTraceToFile(File traceFile, S encapsulatingProto) throws IOException { synchronized (mBufferLock) { traceFile.delete(); try (OutputStream os = new FileOutputStream(traceFile)) { traceFile.setReadable(true /* readable */, false /* ownerOnly */); - os.write(encapsulatingProto.getBytes()); - for (ProtoOutputStream protoOutputStream : mBuffer) { - encapsulatingProto = protoOutputStream; - byte[] protoBytes = encapsulatingProto.getBytes(); - os.write(protoBytes); - } + mProtoProvider.write(encapsulatingProto, mBuffer, os); os.flush(); } } @@ -115,12 +179,16 @@ public class TraceBuffer { while (availableSpace < protoLength) { - ProtoOutputStream item = mBuffer.poll(); + P item = mBuffer.poll(); if (item == null) { throw new IllegalStateException("No element to discard from buffer"); } - mBufferUsedSize -= item.getRawSize(); + mBufferUsedSize -= mProtoProvider.getItemSize(item); availableSpace = getAvailableSpace(); + + if (mProtoDequeuedCallback != null) { + mProtoDequeuedCallback.accept(item); + } } } @@ -129,13 +197,18 @@ public class TraceBuffer { */ public void resetBuffer() { synchronized (mBufferLock) { + if (mProtoDequeuedCallback != null) { + for (T item : mBuffer) { + mProtoDequeuedCallback.accept(item); + } + } mBuffer.clear(); mBufferUsedSize = 0; } } @VisibleForTesting - int getBufferSize() { + public int getBufferSize() { return mBufferUsedSize; } @@ -144,16 +217,9 @@ public class TraceBuffer { */ public String getStatus() { synchronized (mBufferLock) { - return "Buffer size: " - + mBufferCapacity - + " bytes" - + "\n" - + "Buffer usage: " - + mBufferUsedSize - + " bytes" - + "\n" - + "Elements in the buffer: " - + mBuffer.size(); + return "Buffer size: " + mBufferCapacity + " bytes" + "\n" + + "Buffer usage: " + mBufferUsedSize + " bytes" + "\n" + + "Elements in the buffer: " + mBuffer.size(); } } } diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index 5d13cf82141b..b55dc68af558 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -221,7 +221,7 @@ static jobject NativeGetOverlayableInfo(JNIEnv* env, jclass /*clazz*/, jlong ptr return nullptr; } - jstring actor_string = env->NewStringUTF(actor->first.c_str()); + jstring actor_string = env->NewStringUTF(actor->second.c_str()); if (env->ExceptionCheck() || actor_string == nullptr) { jniThrowException(env, "java/io/IOException", "Error reading overlayable from APK"); return 0; diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index fb8e633fec12..e77c25efb1d4 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -537,9 +537,10 @@ public: LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this); if (mObject != NULL) { JNIEnv* env = javavm_to_jnienv(mVM); - jobject jBinderProxy = javaObjectForIBinder(env, who.promote()); + ScopedLocalRef<jobject> jBinderProxy(env, javaObjectForIBinder(env, who.promote())); env->CallStaticVoidMethod(gBinderProxyOffsets.mClass, - gBinderProxyOffsets.mSendDeathNotice, mObject, jBinderProxy); + gBinderProxyOffsets.mSendDeathNotice, mObject, + jBinderProxy.get()); if (env->ExceptionCheck()) { jthrowable excep = env->ExceptionOccurred(); report_exception(env, excep, diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp index f90d1cf27d7c..84acf9ac6989 100644 --- a/core/jni/android_view_InputEventSender.cpp +++ b/core/jni/android_view_InputEventSender.cpp @@ -113,10 +113,13 @@ status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* even } uint32_t publishedSeq = mNextPublishedSeq++; - status_t status = mInputPublisher.publishKeyEvent(publishedSeq, - event->getDeviceId(), event->getSource(), event->getDisplayId(), event->getAction(), - event->getFlags(), event->getKeyCode(), event->getScanCode(), event->getMetaState(), - event->getRepeatCount(), event->getDownTime(), event->getEventTime()); + status_t status = + mInputPublisher.publishKeyEvent(publishedSeq, event->getDeviceId(), event->getSource(), + event->getDisplayId(), event->getHmac(), + event->getAction(), event->getFlags(), + event->getKeyCode(), event->getScanCode(), + event->getMetaState(), event->getRepeatCount(), + event->getDownTime(), event->getEventTime()); if (status) { ALOGW("Failed to send key event on channel '%s'. status=%d", getInputChannelName().c_str(), status); @@ -134,17 +137,24 @@ status_t NativeInputEventSender::sendMotionEvent(uint32_t seq, const MotionEvent uint32_t publishedSeq; for (size_t i = 0; i <= event->getHistorySize(); i++) { publishedSeq = mNextPublishedSeq++; - status_t status = mInputPublisher.publishMotionEvent(publishedSeq, - event->getDeviceId(), event->getSource(), event->getDisplayId(), - event->getAction(), event->getActionButton(), event->getFlags(), - event->getEdgeFlags(), event->getMetaState(), event->getButtonState(), - event->getClassification(), - event->getXOffset(), event->getYOffset(), - event->getXPrecision(), event->getYPrecision(), - event->getRawXCursorPosition(), event->getRawYCursorPosition(), - event->getDownTime(), event->getHistoricalEventTime(i), - event->getPointerCount(), event->getPointerProperties(), - event->getHistoricalRawPointerCoords(0, i)); + status_t status = + mInputPublisher.publishMotionEvent(publishedSeq, event->getDeviceId(), + event->getSource(), event->getDisplayId(), + event->getHmac(), event->getAction(), + event->getActionButton(), event->getFlags(), + event->getEdgeFlags(), event->getMetaState(), + event->getButtonState(), + event->getClassification(), event->getXScale(), + event->getYScale(), event->getXOffset(), + event->getYOffset(), event->getXPrecision(), + event->getYPrecision(), + event->getRawXCursorPosition(), + event->getRawYCursorPosition(), + event->getDownTime(), + event->getHistoricalEventTime(i), + event->getPointerCount(), + event->getPointerProperties(), + event->getHistoricalRawPointerCoords(0, i)); if (status) { ALOGW("Failed to send motion event sample on channel '%s'. status=%d", getInputChannelName().c_str(), status); diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp index f0107723a43e..57979bd9b546 100644 --- a/core/jni/android_view_KeyEvent.cpp +++ b/core/jni/android_view_KeyEvent.cpp @@ -20,15 +20,53 @@ #include <android_runtime/AndroidRuntime.h> #include <android_runtime/Log.h> -#include <utils/Log.h> #include <input/Input.h> +#include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/ScopedUtfChars.h> +#include <utils/Log.h> +#include <optional> #include "android_view_KeyEvent.h" #include "core_jni_helpers.h" namespace android { +/** + * Convert an std::array of bytes into a Java object. + */ +template <size_t N> +static ScopedLocalRef<jbyteArray> toJbyteArray(JNIEnv* env, const std::array<uint8_t, N>& data) { + ScopedLocalRef<jbyteArray> array(env, env->NewByteArray(N)); + if (array.get() == nullptr) { + jniThrowException(env, "java/lang/OutOfMemoryError", nullptr); + return array; + } + static_assert(sizeof(char) == sizeof(uint8_t)); + env->SetByteArrayRegion(array.get(), 0, N, reinterpret_cast<const signed char*>(data.data())); + return array; +} + +/** + * Convert a Java object into an std::array of bytes of size N. + * If the object is null, or the length is unexpected, return std::nullopt. + */ +template <size_t N> +static std::optional<std::array<uint8_t, N>> fromJobject(JNIEnv* env, jobject object) { + if (object == nullptr) { + return std::nullopt; + } + jbyteArray javaArray = reinterpret_cast<jbyteArray>(object); + ScopedByteArrayRO bytes(env, javaArray); + if (bytes.size() != N) { + ALOGE("Could not initialize array from java object, expected length %zu but got %zu", N, + bytes.size()); + return std::nullopt; + } + std::array<uint8_t, N> array; + std::move(bytes.get(), bytes.get() + N, array.begin()); + return array; +} + // ---------------------------------------------------------------------------- static struct { @@ -40,6 +78,7 @@ static struct { jfieldID mDeviceId; jfieldID mSource; jfieldID mDisplayId; + jfieldID mHmac; jfieldID mMetaState; jfieldID mAction; jfieldID mKeyCode; @@ -54,20 +93,16 @@ static struct { // ---------------------------------------------------------------------------- jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) { - jobject eventObj = env->CallStaticObjectMethod(gKeyEventClassInfo.clazz, - gKeyEventClassInfo.obtain, - nanoseconds_to_milliseconds(event->getDownTime()), - nanoseconds_to_milliseconds(event->getEventTime()), - event->getAction(), - event->getKeyCode(), - event->getRepeatCount(), - event->getMetaState(), - event->getDeviceId(), - event->getScanCode(), - event->getFlags(), - event->getSource(), - event->getDisplayId(), - NULL); + ScopedLocalRef<jbyteArray> hmac = toJbyteArray(env, event->getHmac()); + jobject eventObj = + env->CallStaticObjectMethod(gKeyEventClassInfo.clazz, gKeyEventClassInfo.obtain, + nanoseconds_to_milliseconds(event->getDownTime()), + nanoseconds_to_milliseconds(event->getEventTime()), + event->getAction(), event->getKeyCode(), + event->getRepeatCount(), event->getMetaState(), + event->getDeviceId(), event->getScanCode(), + event->getFlags(), event->getSource(), + event->getDisplayId(), hmac.get(), NULL); if (env->ExceptionCheck()) { ALOGE("An exception occurred while obtaining a key event."); LOGE_EX(env); @@ -82,6 +117,11 @@ status_t android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj, jint deviceId = env->GetIntField(eventObj, gKeyEventClassInfo.mDeviceId); jint source = env->GetIntField(eventObj, gKeyEventClassInfo.mSource); jint displayId = env->GetIntField(eventObj, gKeyEventClassInfo.mDisplayId); + jobject hmacObj = env->GetObjectField(eventObj, gKeyEventClassInfo.mHmac); + std::optional<std::array<uint8_t, 32>> hmac = fromJobject<32>(env, hmacObj); + if (!hmac) { + hmac = INVALID_HMAC; + } jint metaState = env->GetIntField(eventObj, gKeyEventClassInfo.mMetaState); jint action = env->GetIntField(eventObj, gKeyEventClassInfo.mAction); jint keyCode = env->GetIntField(eventObj, gKeyEventClassInfo.mKeyCode); @@ -91,10 +131,9 @@ status_t android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj, jlong downTime = env->GetLongField(eventObj, gKeyEventClassInfo.mDownTime); jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime); - event->initialize(deviceId, source, displayId, action, flags, keyCode, scanCode, metaState, - repeatCount, - milliseconds_to_nanoseconds(downTime), - milliseconds_to_nanoseconds(eventTime)); + event->initialize(deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode, + metaState, repeatCount, milliseconds_to_nanoseconds(downTime), + milliseconds_to_nanoseconds(eventTime)); return OK; } @@ -134,8 +173,9 @@ int register_android_view_KeyEvent(JNIEnv* env) { jclass clazz = FindClassOrDie(env, "android/view/KeyEvent"); gKeyEventClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); - gKeyEventClassInfo.obtain = GetStaticMethodIDOrDie(env, gKeyEventClassInfo.clazz, - "obtain", "(JJIIIIIIIIILjava/lang/String;)Landroid/view/KeyEvent;"); + gKeyEventClassInfo.obtain = + GetStaticMethodIDOrDie(env, gKeyEventClassInfo.clazz, "obtain", + "(JJIIIIIIIII[BLjava/lang/String;)Landroid/view/KeyEvent;"); gKeyEventClassInfo.recycle = GetMethodIDOrDie(env, gKeyEventClassInfo.clazz, "recycle", "()V"); @@ -143,6 +183,7 @@ int register_android_view_KeyEvent(JNIEnv* env) { gKeyEventClassInfo.mSource = GetFieldIDOrDie(env, gKeyEventClassInfo.clazz, "mSource", "I"); gKeyEventClassInfo.mDisplayId = GetFieldIDOrDie(env, gKeyEventClassInfo.clazz, "mDisplayId", "I"); + gKeyEventClassInfo.mHmac = GetFieldIDOrDie(env, gKeyEventClassInfo.clazz, "mHmac", "[B"); gKeyEventClassInfo.mMetaState = GetFieldIDOrDie(env, gKeyEventClassInfo.clazz, "mMetaState", "I"); gKeyEventClassInfo.mAction = GetFieldIDOrDie(env, gKeyEventClassInfo.clazz, "mAction", "I"); diff --git a/core/jni/android_view_KeyEvent.h b/core/jni/android_view_KeyEvent.h index 586eb2f2a139..dab6bb75c91e 100644 --- a/core/jni/android_view_KeyEvent.h +++ b/core/jni/android_view_KeyEvent.h @@ -42,4 +42,4 @@ extern status_t android_view_KeyEvent_recycle(JNIEnv* env, jobject eventObj); } // namespace android -#endif // _ANDROID_OS_KEYEVENT_H +#endif // _ANDROID_VIEW_KEYEVENT_H diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index feb9fe39e0a8..3335fb23cfb7 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -330,14 +330,12 @@ static void pointerPropertiesFromNative(JNIEnv* env, const PointerProperties* po // ---------------------------------------------------------------------------- -static jlong android_view_MotionEvent_nativeInitialize(JNIEnv* env, jclass clazz, - jlong nativePtr, - jint deviceId, jint source, jint displayId, jint action, jint flags, jint edgeFlags, - jint metaState, jint buttonState, jint classification, - jfloat xOffset, jfloat yOffset, jfloat xPrecision, jfloat yPrecision, - jlong downTimeNanos, jlong eventTimeNanos, - jint pointerCount, jobjectArray pointerPropertiesObjArray, - jobjectArray pointerCoordsObjArray) { +static jlong android_view_MotionEvent_nativeInitialize( + JNIEnv* env, jclass clazz, jlong nativePtr, jint deviceId, jint source, jint displayId, + jint action, jint flags, jint edgeFlags, jint metaState, jint buttonState, + jint classification, jfloat xOffset, jfloat yOffset, jfloat xPrecision, jfloat yPrecision, + jlong downTimeNanos, jlong eventTimeNanos, jint pointerCount, + jobjectArray pointerPropertiesObjArray, jobjectArray pointerCoordsObjArray) { if (!validatePointerCount(env, pointerCount) || !validatePointerPropertiesArray(env, pointerPropertiesObjArray, pointerCount) || !validatePointerCoordsObjArray(env, pointerCoordsObjArray, pointerCount)) { @@ -371,11 +369,12 @@ static jlong android_view_MotionEvent_nativeInitialize(JNIEnv* env, jclass clazz env->DeleteLocalRef(pointerCoordsObj); } - event->initialize(deviceId, source, displayId, action, 0, flags, edgeFlags, metaState, - buttonState, static_cast<MotionClassification>(classification), - xOffset, yOffset, xPrecision, yPrecision, - AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, - downTimeNanos, eventTimeNanos, pointerCount, pointerProperties, rawPointerCoords); + event->initialize(deviceId, source, displayId, INVALID_HMAC, action, 0, flags, edgeFlags, + metaState, buttonState, static_cast<MotionClassification>(classification), + 1 /*xScale*/, 1 /*yScale*/, xOffset, yOffset, xPrecision, yPrecision, + AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, + downTimeNanos, eventTimeNanos, pointerCount, pointerProperties, + rawPointerCoords); return reinterpret_cast<jlong>(event); @@ -757,155 +756,76 @@ static void android_view_MotionEvent_nativeScale(jlong nativePtr, jfloat scale) // ---------------------------------------------------------------------------- static const JNINativeMethod gMotionEventMethods[] = { - /* name, signature, funcPtr */ - { "nativeInitialize", - "(JIIIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;" - "[Landroid/view/MotionEvent$PointerCoords;)J", - (void*)android_view_MotionEvent_nativeInitialize }, - { "nativeDispose", - "(J)V", - (void*)android_view_MotionEvent_nativeDispose }, - { "nativeAddBatch", - "(JJ[Landroid/view/MotionEvent$PointerCoords;I)V", - (void*)android_view_MotionEvent_nativeAddBatch }, - { "nativeReadFromParcel", - "(JLandroid/os/Parcel;)J", - (void*)android_view_MotionEvent_nativeReadFromParcel }, - { "nativeWriteToParcel", - "(JLandroid/os/Parcel;)V", - (void*)android_view_MotionEvent_nativeWriteToParcel }, - { "nativeAxisToString", "(I)Ljava/lang/String;", - (void*)android_view_MotionEvent_nativeAxisToString }, - { "nativeAxisFromString", "(Ljava/lang/String;)I", - (void*)android_view_MotionEvent_nativeAxisFromString }, - { "nativeGetPointerProperties", - "(JILandroid/view/MotionEvent$PointerProperties;)V", - (void*)android_view_MotionEvent_nativeGetPointerProperties }, - { "nativeGetPointerCoords", - "(JIILandroid/view/MotionEvent$PointerCoords;)V", - (void*)android_view_MotionEvent_nativeGetPointerCoords }, - - // --------------- @FastNative ---------------------- - { "nativeGetPointerId", - "(JI)I", - (void*)android_view_MotionEvent_nativeGetPointerId }, - { "nativeGetToolType", - "(JI)I", - (void*)android_view_MotionEvent_nativeGetToolType }, - { "nativeGetEventTimeNanos", - "(JI)J", - (void*)android_view_MotionEvent_nativeGetEventTimeNanos }, - { "nativeGetRawAxisValue", - "(JIII)F", - (void*)android_view_MotionEvent_nativeGetRawAxisValue }, - { "nativeGetAxisValue", - "(JIII)F", - (void*)android_view_MotionEvent_nativeGetAxisValue }, - { "nativeTransform", - "(JLandroid/graphics/Matrix;)V", - (void*)android_view_MotionEvent_nativeTransform }, - - // --------------- @CriticalNative ------------------ - - { "nativeCopy", - "(JJZ)J", - (void*)android_view_MotionEvent_nativeCopy }, - { "nativeGetDeviceId", - "(J)I", - (void*)android_view_MotionEvent_nativeGetDeviceId }, - { "nativeGetSource", - "(J)I", - (void*)android_view_MotionEvent_nativeGetSource }, - { "nativeSetSource", - "(JI)V", - (void*)android_view_MotionEvent_nativeSetSource }, - { "nativeGetDisplayId", - "(J)I", - (void*)android_view_MotionEvent_nativeGetDisplayId }, - { "nativeSetDisplayId", - "(JI)V", - (void*)android_view_MotionEvent_nativeSetDisplayId }, - { "nativeGetAction", - "(J)I", - (void*)android_view_MotionEvent_nativeGetAction }, - { "nativeSetAction", - "(JI)V", - (void*)android_view_MotionEvent_nativeSetAction }, - { "nativeGetActionButton", - "(J)I", - (void*)android_view_MotionEvent_nativeGetActionButton}, - { "nativeSetActionButton", - "(JI)V", - (void*)android_view_MotionEvent_nativeSetActionButton}, - { "nativeIsTouchEvent", - "(J)Z", - (void*)android_view_MotionEvent_nativeIsTouchEvent }, - { "nativeGetFlags", - "(J)I", - (void*)android_view_MotionEvent_nativeGetFlags }, - { "nativeSetFlags", - "(JI)V", - (void*)android_view_MotionEvent_nativeSetFlags }, - { "nativeGetEdgeFlags", - "(J)I", - (void*)android_view_MotionEvent_nativeGetEdgeFlags }, - { "nativeSetEdgeFlags", - "(JI)V", - (void*)android_view_MotionEvent_nativeSetEdgeFlags }, - { "nativeGetMetaState", - "(J)I", - (void*)android_view_MotionEvent_nativeGetMetaState }, - { "nativeGetButtonState", - "(J)I", - (void*)android_view_MotionEvent_nativeGetButtonState }, - { "nativeSetButtonState", - "(JI)V", - (void*)android_view_MotionEvent_nativeSetButtonState }, - { "nativeGetClassification", - "(J)I", - (void*)android_view_MotionEvent_nativeGetClassification }, - { "nativeOffsetLocation", - "(JFF)V", - (void*)android_view_MotionEvent_nativeOffsetLocation }, - { "nativeGetXOffset", - "(J)F", - (void*)android_view_MotionEvent_nativeGetXOffset }, - { "nativeGetYOffset", - "(J)F", - (void*)android_view_MotionEvent_nativeGetYOffset }, - { "nativeGetXPrecision", - "(J)F", - (void*)android_view_MotionEvent_nativeGetXPrecision }, - { "nativeGetYPrecision", - "(J)F", - (void*)android_view_MotionEvent_nativeGetYPrecision }, - { "nativeGetXCursorPosition", - "(J)F", - (void*)android_view_MotionEvent_nativeGetXCursorPosition }, - { "nativeGetYCursorPosition", - "(J)F", - (void*)android_view_MotionEvent_nativeGetYCursorPosition }, - { "nativeSetCursorPosition", - "(JFF)V", - (void*)android_view_MotionEvent_nativeSetCursorPosition }, - { "nativeGetDownTimeNanos", - "(J)J", - (void*)android_view_MotionEvent_nativeGetDownTimeNanos }, - { "nativeSetDownTimeNanos", - "(JJ)V", - (void*)android_view_MotionEvent_nativeSetDownTimeNanos }, - { "nativeGetPointerCount", - "(J)I", - (void*)android_view_MotionEvent_nativeGetPointerCount }, - { "nativeFindPointerIndex", - "(JI)I", - (void*)android_view_MotionEvent_nativeFindPointerIndex }, - { "nativeGetHistorySize", - "(J)I", - (void*)android_view_MotionEvent_nativeGetHistorySize }, - { "nativeScale", - "(JF)V", - (void*)android_view_MotionEvent_nativeScale }, + /* name, signature, funcPtr */ + {"nativeInitialize", + "(JIIIIIIIIIFFFFJJI[Landroid/view/MotionEvent$PointerProperties;" + "[Landroid/view/MotionEvent$PointerCoords;)J", + (void*)android_view_MotionEvent_nativeInitialize}, + {"nativeDispose", "(J)V", (void*)android_view_MotionEvent_nativeDispose}, + {"nativeAddBatch", "(JJ[Landroid/view/MotionEvent$PointerCoords;I)V", + (void*)android_view_MotionEvent_nativeAddBatch}, + {"nativeReadFromParcel", "(JLandroid/os/Parcel;)J", + (void*)android_view_MotionEvent_nativeReadFromParcel}, + {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", + (void*)android_view_MotionEvent_nativeWriteToParcel}, + {"nativeAxisToString", "(I)Ljava/lang/String;", + (void*)android_view_MotionEvent_nativeAxisToString}, + {"nativeAxisFromString", "(Ljava/lang/String;)I", + (void*)android_view_MotionEvent_nativeAxisFromString}, + {"nativeGetPointerProperties", "(JILandroid/view/MotionEvent$PointerProperties;)V", + (void*)android_view_MotionEvent_nativeGetPointerProperties}, + {"nativeGetPointerCoords", "(JIILandroid/view/MotionEvent$PointerCoords;)V", + (void*)android_view_MotionEvent_nativeGetPointerCoords}, + + // --------------- @FastNative ---------------------- + {"nativeGetPointerId", "(JI)I", (void*)android_view_MotionEvent_nativeGetPointerId}, + {"nativeGetToolType", "(JI)I", (void*)android_view_MotionEvent_nativeGetToolType}, + {"nativeGetEventTimeNanos", "(JI)J", + (void*)android_view_MotionEvent_nativeGetEventTimeNanos}, + {"nativeGetRawAxisValue", "(JIII)F", (void*)android_view_MotionEvent_nativeGetRawAxisValue}, + {"nativeGetAxisValue", "(JIII)F", (void*)android_view_MotionEvent_nativeGetAxisValue}, + {"nativeTransform", "(JLandroid/graphics/Matrix;)V", + (void*)android_view_MotionEvent_nativeTransform}, + + // --------------- @CriticalNative ------------------ + + {"nativeCopy", "(JJZ)J", (void*)android_view_MotionEvent_nativeCopy}, + {"nativeGetDeviceId", "(J)I", (void*)android_view_MotionEvent_nativeGetDeviceId}, + {"nativeGetSource", "(J)I", (void*)android_view_MotionEvent_nativeGetSource}, + {"nativeSetSource", "(JI)V", (void*)android_view_MotionEvent_nativeSetSource}, + {"nativeGetDisplayId", "(J)I", (void*)android_view_MotionEvent_nativeGetDisplayId}, + {"nativeSetDisplayId", "(JI)V", (void*)android_view_MotionEvent_nativeSetDisplayId}, + {"nativeGetAction", "(J)I", (void*)android_view_MotionEvent_nativeGetAction}, + {"nativeSetAction", "(JI)V", (void*)android_view_MotionEvent_nativeSetAction}, + {"nativeGetActionButton", "(J)I", (void*)android_view_MotionEvent_nativeGetActionButton}, + {"nativeSetActionButton", "(JI)V", (void*)android_view_MotionEvent_nativeSetActionButton}, + {"nativeIsTouchEvent", "(J)Z", (void*)android_view_MotionEvent_nativeIsTouchEvent}, + {"nativeGetFlags", "(J)I", (void*)android_view_MotionEvent_nativeGetFlags}, + {"nativeSetFlags", "(JI)V", (void*)android_view_MotionEvent_nativeSetFlags}, + {"nativeGetEdgeFlags", "(J)I", (void*)android_view_MotionEvent_nativeGetEdgeFlags}, + {"nativeSetEdgeFlags", "(JI)V", (void*)android_view_MotionEvent_nativeSetEdgeFlags}, + {"nativeGetMetaState", "(J)I", (void*)android_view_MotionEvent_nativeGetMetaState}, + {"nativeGetButtonState", "(J)I", (void*)android_view_MotionEvent_nativeGetButtonState}, + {"nativeSetButtonState", "(JI)V", (void*)android_view_MotionEvent_nativeSetButtonState}, + {"nativeGetClassification", "(J)I", + (void*)android_view_MotionEvent_nativeGetClassification}, + {"nativeOffsetLocation", "(JFF)V", (void*)android_view_MotionEvent_nativeOffsetLocation}, + {"nativeGetXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetXOffset}, + {"nativeGetYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetYOffset}, + {"nativeGetXPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetXPrecision}, + {"nativeGetYPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetYPrecision}, + {"nativeGetXCursorPosition", "(J)F", + (void*)android_view_MotionEvent_nativeGetXCursorPosition}, + {"nativeGetYCursorPosition", "(J)F", + (void*)android_view_MotionEvent_nativeGetYCursorPosition}, + {"nativeSetCursorPosition", "(JFF)V", + (void*)android_view_MotionEvent_nativeSetCursorPosition}, + {"nativeGetDownTimeNanos", "(J)J", (void*)android_view_MotionEvent_nativeGetDownTimeNanos}, + {"nativeSetDownTimeNanos", "(JJ)V", (void*)android_view_MotionEvent_nativeSetDownTimeNanos}, + {"nativeGetPointerCount", "(J)I", (void*)android_view_MotionEvent_nativeGetPointerCount}, + {"nativeFindPointerIndex", "(JI)I", (void*)android_view_MotionEvent_nativeFindPointerIndex}, + {"nativeGetHistorySize", "(J)I", (void*)android_view_MotionEvent_nativeGetHistorySize}, + {"nativeScale", "(JF)V", (void*)android_view_MotionEvent_nativeScale}, }; int register_android_view_MotionEvent(JNIEnv* env) { diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h index 0cf1fb2f2203..9ce4bf367688 100644 --- a/core/jni/android_view_MotionEvent.h +++ b/core/jni/android_view_MotionEvent.h @@ -38,4 +38,4 @@ extern status_t android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj); } // namespace android -#endif // _ANDROID_OS_KEYEVENT_H +#endif // _ANDROID_VIEW_MOTIONEVENT_H diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 60f2fc8e081f..1426932ec04a 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -109,7 +109,7 @@ message TaskRecordProto { // To be removed soon. optional .com.android.server.wm.ConfigurationContainerProto configuration_container = 1 [deprecated=true]; optional int32 id = 2; - repeated ActivityRecordProto activities = 3; + repeated .com.android.server.wm.ActivityRecordProto activities = 3; optional int32 stack_id = 4; optional .android.graphics.RectProto last_non_fullscreen_bounds = 5; optional string real_activity = 6; @@ -123,21 +123,6 @@ message TaskRecordProto { optional .com.android.server.wm.TaskProto task = 14; } -message ActivityRecordProto { - option (.android.msg_privacy).dest = DEST_AUTOMATIC; - - // To be removed soon. - optional .com.android.server.wm.ConfigurationContainerProto configuration_container = 1 [deprecated=true]; - optional .com.android.server.wm.IdentifierProto identifier = 2; - optional string state = 3; - optional bool visible_requested = 4; - optional bool front_of_task = 5; - optional int32 proc_id = 6; - optional bool translucent = 7; - optional .com.android.server.wm.AppWindowTokenProto app_window_token = 8; - optional bool visible = 9; -} - message KeyguardControllerProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 0c7484216367..c0743e58b5c5 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -147,7 +147,8 @@ message DisplayContentProto { optional int32 id = 2; repeated StackProto stacks = 3; optional DockedStackDividerControllerProto docked_stack_divider_controller = 4; - optional PinnedStackControllerProto pinned_stack_controller = 5; + // Will be removed soon. + optional PinnedStackControllerProto pinned_stack_controller = 5 [deprecated=true]; /* non app windows */ repeated WindowTokenProto above_app_windows = 6; repeated WindowTokenProto below_app_windows = 7; @@ -184,8 +185,8 @@ message DockedStackDividerControllerProto { message PinnedStackControllerProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional .android.graphics.RectProto default_bounds = 1; - optional .android.graphics.RectProto movement_bounds = 2; + optional .android.graphics.RectProto default_bounds = 1 [deprecated=true]; + optional .android.graphics.RectProto movement_bounds = 2 [deprecated=true]; } /* represents TaskStack */ @@ -213,7 +214,7 @@ message TaskProto { optional WindowContainerProto window_container = 1; optional int32 id = 2; - repeated AppWindowTokenProto app_window_tokens = 3; + repeated ActivityRecordProto activity = 3; optional bool fills_parent = 4; optional .android.graphics.RectProto bounds = 5; optional .android.graphics.RectProto displayed_bounds = 6; @@ -223,12 +224,12 @@ message TaskProto { optional int32 surface_height = 9; } -/* represents AppWindowToken */ -message AppWindowTokenProto { +/* represents ActivityRecordProto */ +message ActivityRecordProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - /* obtained from ActivityRecord */ optional string name = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional WindowTokenProto window_token = 2; optional bool last_surface_showing = 3; optional bool is_waiting_for_transition_start = 4; @@ -253,6 +254,13 @@ message AppWindowTokenProto { optional bool visible_set_from_transferred_starting_window = 22; repeated .android.graphics.RectProto frozen_bounds = 23; optional bool visible = 24; + // To be removed soon. + optional .com.android.server.wm.ConfigurationContainerProto configuration_container = 25 [deprecated=true]; + optional .com.android.server.wm.IdentifierProto identifier = 26; + optional string state = 27; + optional bool front_of_task = 28; + optional int32 proc_id = 29; + optional bool translucent = 30; } /* represents WindowToken */ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e9d5b2b918f7..181a32d06233 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -350,6 +350,8 @@ <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP" /> <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP" /> <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED" /> + <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_CARRIER" /> + <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_CARRIER" /> <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" /> <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" /> <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" /> @@ -4856,6 +4858,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" /> + <!-- 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" /> + <!-- 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" /> + <!-- Allows input events to be monitored. Very dangerous! @hide --> <permission android:name="android.permission.MONITOR_INPUT" android:protectionLevel="signature" /> diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index de8f55bb8036..c34a485bc7a9 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Jy het jou ontsluitpatroon <xliff:g id="NUMBER_0">%1$d</xliff:g> keer verkeerdelik geteken. Na nog <xliff:g id="NUMBER_1">%2$d</xliff:g> onsuksesvolle pogings, sal jy gevra word om jou foon te ontsluit deur middel van \'n e-posrekening.\n\n Probeer weer oor <xliff:g id="NUMBER_2">%3$d</xliff:g> sekondes."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Verwyder"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Verhoog volume bo aanbevole vlak?\n\nOm lang tydperke teen hoë volume te luister, kan jou gehoor beskadig."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gebruik toeganklikheidkortpad?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Wanneer die kortpad aan is, sal \'n toeganklikheidkenmerk begin word as albei volumeknoppies 3 sekondes lank gedruk word.\n\n Bestaande toeganklikheidkenmerk:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Jy kan die kenmerk in Instellings > Toeganklikheid verander."</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index ec9abfe3e67f..23e6ccdbd87f 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"የመክፈቻ ስርዓተ ጥለቱን <xliff:g id="NUMBER_0">%1$d</xliff:g> ጊዜ በትክክል አልሳሉትም። ከ<xliff:g id="NUMBER_1">%2$d</xliff:g> ተጨማሪ ያልተሳኩ ሙከራዎች በኋላ የኢሜይል መለያ ተጠቅመው ስልክዎን እንዲከፍቱ ይጠየቃሉ።\n\nእባክዎ ከ<xliff:g id="NUMBER_2">%3$d</xliff:g> ሰከንዶች በኋላ እንደገና ይሞክሩ።"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"አስወግድ"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ድምጹ ከሚመከረው መጠን በላይ ከፍ ይበል?\n\nበከፍተኛ ድምጽ ለረጅም ጊዜ ማዳመጥ ጆሮዎን ሊጎዳው ይችላል።"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"የተደራሽነት አቋራጭ ጥቅም ላይ ይዋል?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"አቋራጩ ሲበራ ሁለቱንም የድምፅ አዝራሮች ለ3 ሰከንዶች ተጭኖ መቆየት የተደራሽነት ባህሪን ያስጀምረዋል።\n\n አሁን ያለ የተደራሽነት ባህሪ፦\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ባህሪውን በቅንብሮች > ተደራሽነት ውስጥ ሊለውጡት ይችላሉ።"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 0d70a11f2eeb..80c04ebee695 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -222,7 +222,7 @@ <string name="reboot_to_update_prepare" msgid="6978842143587422365">"جارٍ الإعداد للتحديث…"</string> <string name="reboot_to_update_package" msgid="4644104795527534811">"جارٍ معالجة حزمة التحديث…"</string> <string name="reboot_to_update_reboot" msgid="4474726009984452312">"جارٍ إعادة التشغيل…"</string> - <string name="reboot_to_reset_title" msgid="2226229680017882787">"إعادة الضبط بحسب بيانات المصنع"</string> + <string name="reboot_to_reset_title" msgid="2226229680017882787">"إعادة الضبط على الإعدادات الأصلية"</string> <string name="reboot_to_reset_message" msgid="3347690497972074356">"جارٍ إعادة التشغيل…"</string> <string name="shutdown_progress" msgid="5017145516412657345">"جارٍ إيقاف التشغيل..."</string> <string name="shutdown_confirm" product="tablet" msgid="2872769463279602432">"سيتم إيقاف تشغيل الجهاز اللوحي."</string> @@ -320,7 +320,7 @@ <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"استرداد محتوى النافذة"</string> <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"فحص محتوى نافذة يتم التفاعل معها"</string> <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"تشغيل الاستكشاف باللمس"</string> - <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"سيتم نطق العناصر التي تم النقر عليها بصوت عال ويمكن استكشاف الشاشة باستخدام الإيماءات."</string> + <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"سيتم قول العناصر التي تم النقر عليها بصوت عال ويمكن استكشاف الشاشة باستخدام الإيماءات."</string> <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"ملاحظة النص الذي تكتبه"</string> <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"يتضمن بيانات شخصية مثل أرقام بطاقات الائتمان وكلمات المرور."</string> <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"التحكم في تكبير الشاشة"</string> @@ -676,8 +676,8 @@ <string name="policylab_forceLock" msgid="7360335502968476434">"قفل الشاشة"</string> <string name="policydesc_forceLock" msgid="1008844760853899693">"التحكّم في طريقة ووقت قفل الشاشة"</string> <string name="policylab_wipeData" msgid="1359485247727537311">"محو جميع البيانات"</string> - <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"يمكنك محو بيانات الجهاز اللوحي بدون تحذير، وذلك عبر إجراء إعادة الضبط بحسب بيانات المصنع."</string> - <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"يمكنك محو بيانات جهاز Android TV بدون تحذير عن طريق تنفيذ إعادة الضبط بحسب بيانات المصنع."</string> + <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"يمكنك محو بيانات الجهاز اللوحي بدون تحذير، وذلك عبر إجراء إعادة الضبط على الإعدادات الأصلية."</string> + <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"يمكنك محو بيانات جهاز Android TV بدون تحذير عن طريق تنفيذ إعادة الضبط على الإعدادات الأصلية."</string> <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"محو بيانات الهاتف بدون تحذير، وذلك من خلال إعادة ضبط البيانات على الإعدادات الأصلية"</string> <string name="policylab_wipeData_secondaryUser" msgid="413813645323433166">"محو بيانات المستخدم"</string> <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"لمحو بيانات هذا المستخدم على هذا الجهاز اللوحي بدون تحذير."</string> @@ -1701,6 +1701,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"لقد رسمت نقش فتح القفل بشكل غير صحيح <xliff:g id="NUMBER_0">%1$d</xliff:g> مرة. بعد إجراء <xliff:g id="NUMBER_1">%2$d</xliff:g> من المحاولات غير الناجحة الأخرى، ستُطالب بإلغاء تأمين الهاتف باستخدام حساب بريد إلكتروني لإلغاء تأمين الهاتف.\n\n أعد المحاولة خلال <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانية."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"إزالة"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"هل تريد رفع مستوى الصوت فوق المستوى الموصى به؟\n\nقد يضر سماع صوت عالٍ لفترات طويلة بسمعك."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"هل تريد استخدام اختصار \"سهولة الاستخدام\"؟"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"عند تشغيل الاختصار، يؤدي الضغط على زرّي مستوى الصوت لمدة 3 ثوانٍ إلى تفعيل ميزة \"سهولة الاستخدام\".\n\n ميزة \"سهولة الاستخدام\" الحالية:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n يمكنك تغيير الميزة من \"الإعدادات\" > \"سهولة الاستخدام\"."</string> @@ -1932,7 +1934,7 @@ <string name="zen_mode_default_events_name" msgid="2280682960128512257">"حدث"</string> <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"النوم"</string> <string name="muted_by" msgid="91464083490094950">"يعمل <xliff:g id="THIRD_PARTY">%1$s</xliff:g> على كتم بعض الأصوات."</string> - <string name="system_error_wipe_data" msgid="5910572292172208493">"حدثت مشكلة داخلية في جهازك، وقد لا يستقر وضعه حتى إجراء إعادة الضبط بحسب بيانات المصنع."</string> + <string name="system_error_wipe_data" msgid="5910572292172208493">"حدثت مشكلة داخلية في جهازك، وقد لا يستقر وضعه حتى إجراء إعادة الضبط على الإعدادات الأصلية."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"حدثت مشكلة داخلية في جهازك. يمكنك الاتصال بالمصنِّع للحصول على تفاصيل."</string> <string name="stk_cc_ussd_to_dial" msgid="3139884150741157610">"تم تغيير طلب USSD إلى مكالمة عادية."</string> <string name="stk_cc_ussd_to_ss" msgid="4826846653052609738">"تم تغيير طلب USSD إلى طلب SS."</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index ad11a37524a0..73e32b85c224 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"আপুনি আপোনাৰ ল\'ক খোলাৰ আৰ্হিটো <xliff:g id="NUMBER_0">%1$d</xliff:g>বাৰ ভুলকৈ আঁকিছে। <xliff:g id="NUMBER_1">%2$d</xliff:g>তকৈ বেছি বাৰ ভুল আৰ্হি আঁকিলে আপোনাৰ ফ\'নটো কোনো একাউণ্টৰ জৰিয়তে আনলক কৰিবলৈ কোৱা হ\'ব।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ছেকেণ্ডৰ পিছত আকৌ চেষ্টা কৰক।"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"আঁতৰাওক"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"অনুমোদিত স্তৰতকৈ ওপৰলৈ ভলিউম বঢ়াব নেকি?\n\nদীৰ্ঘ সময়ৰ বাবে উচ্চ ভলিউমত শুনাৰ ফলত শ্ৰৱণ ক্ষমতাৰ ক্ষতি হ\'ব পাৰে।"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"দিব্যাংগসকলৰ সুবিধাৰ শ্বৰ্টকাট ব্যৱহাৰ কৰেনে?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"শ্বৰ্টকাট অন হৈ থকাৰ সময়ত দুয়োটা ভলিউম বুটামত ৩ ছেকেণ্ডৰ বাবে ছাপ দি থাকিলে দিব্যাংগসকলৰ বাবে থকা সুবিধা এটা আৰম্ভ হ\'ব। \n\n চলিত দিব্যাংগসকলৰ সুবিধা:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n আপুনি এই সুবিধাটো ছেটিংসমূহ > দিব্যাংগসকলৰ বাবে সুবিধা-লৈ গৈ সলনি কৰিব পাৰে।"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index a535f1f0e2ba..c894c29956dc 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Siz artıq modeli <xliff:g id="NUMBER_0">%1$d</xliff:g> dəfə yanlış daxil etmisiniz.<xliff:g id="NUMBER_1">%2$d</xliff:g> dəfə də yanlış daxil etsəniz, telefonun kilidinin açılması üçün elektron poçt ünvanınız tələb olunacaq.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> saniyə ərzində yenidən cəhd edin."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" - "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Yığışdır"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Səsin həcmi tövsiyə olunan səviyyədən artıq olsun?\n\nYüksək səsi uzun zaman dinləmək eşitmə qabiliyyətinizə zərər vura bilər."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Əlçatımlılıq Qısayolu istifadə edilsin?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Qısayol aktiv olduqda hər iki səs düyməsinə 3 saniyə basıb saxlamaqla əlçatımlılıq funksiyası işə başlayacaq.\n\n Cari əlçatımlılıq funksiyası:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funksiyanı Ayarlar və Əçatımlılıq bölməsində dəyişə bilərsiniz."</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 249e5af2d585..909c31558e18 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -1635,6 +1635,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Nacrtali ste šablon za otključavanje netačno <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Posle još <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešna(ih) pokušaja, od vas će biti zatraženo da otključate telefon pomoću naloga e-pošte.\n\nProbajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunde/i."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Ukloni"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite da pojačate zvuk iznad preporučenog nivoa?\n\nSlušanje glasne muzike duže vreme može da vam ošteti sluh."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li da koristite prečicu za pristupačnost?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kada je prečica uključena, pritisnite oba dugmeta za jačinu zvuka da biste pokrenuli funkciju pristupačnosti.\n\n Aktuelna funkcija pristupačnosti:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Možete da promenite funkciju u odeljku Podešavanja > Pristupačnost."</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index dc4c56e684ee..19dcf927b74d 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1657,6 +1657,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Вы няправільна ўвялі графічны ключ разблакiроўкi пэўную колькасць разоў: <xliff:g id="NUMBER_0">%1$d</xliff:g>. Пасля яшчэ некалькiх няўдалых спроб (<xliff:g id="NUMBER_1">%2$d</xliff:g>) вам будзе прапанавана разблакiраваць тэлефон, увайшоўшы ў Google.\n\n Паўтарыце спробу праз <xliff:g id="NUMBER_2">%3$d</xliff:g> с."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Выдалiць"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Павялiчыць гук вышэй рэкамендаванага ўзроўню?\n\nДоўгае праслухоўванне музыкi на вялiкай гучнасцi можа пашкодзiць ваш слых."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Выкарыстоўваць камбінацыю хуткага доступу для спецыяльных магчымасцей?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Калі камбінацыя хуткага доступу ўключана, вы можаце націснуць абедзве кнопкі гучнасці і ўтрымліваць іх 3 секунды, каб уключыць функцыю спецыяльных магчымасцей.\n\n Бягучая функцыя спецыяльных магчымасцей:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Вы можаце змяніць гэту функцыю ў меню \"Налады > Спецыяльныя магчымасці\"."</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 30b7d302a273..8a687666ac9b 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Начертахте неправилно фигурата си за отключване <xliff:g id="NUMBER_0">%1$d</xliff:g> пъти. След още <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешни опита ще бъдете помолени да отключите телефона посредством имейл адрес.\n\n Опитайте отново след <xliff:g id="NUMBER_2">%3$d</xliff:g> секунди."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Премахване"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Да се увеличи ли силата на звука над препоръчителното ниво?\n\nПродължителното слушане при висока сила на звука може да увреди слуха ви."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Искате ли да използвате пряк път към функцията за достъпност?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Когато прекият път е включен, можете да стартирате дадена функция за достъпност, като натиснете двата бутона за промяна на силата на звука и ги задържите 3 секунди.\n\n Текущата функция за достъпност е:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Можете да промените функцията от „Настройки“ > „Достъпност“."</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index d85b5d6ee716..9ca383d30b04 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"আপনি আপনার আনলকের প্যাটার্ন আঁকার ক্ষেত্রে <xliff:g id="NUMBER_0">%1$d</xliff:g> বার ভুল করেছেন৷ আর <xliff:g id="NUMBER_1">%2$d</xliff:g> বার অসফল প্রচেষ্টা করা হলে আপনাকে একটি ইমেল অ্যাকাউন্ট মারফত আপনার ফোন আনলক করতে বলা হবে৷\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> সেকেন্ডের মধ্যে আবার চেষ্টা করুন৷"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"সরান"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"প্রস্তাবিত স্তরের চেয়ে বেশি উঁচুতে ভলিউম বাড়াবেন?\n\nউঁচু ভলিউমে বেশি সময় ধরে কিছু শুনলে আপনার শ্রবনশক্তির ক্ষতি হতে পারে।"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"অ্যাক্সেসযোগ্যতা শর্টকাট ব্যবহার করবেন?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"শর্টকাটটি চালু থাকলে দুটি ভলিউম বোতাম একসাথে ৩ সেকেন্ড টিপে ধরে রাখলে একটি অ্যাকসেসিবিলিটি বৈশিষ্ট্য চালু হবে।\n\n বর্তমান অ্যাকসেসিবিলিটি বৈশিষ্ট্য:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n আপনি এই বৈশিষ্ট্যটি সেটিংস > অ্যাকসেসিবিলিটিতে গিয়ে পরিবর্তন করতে পারবেন।"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index c8c65bb15eb0..5f83aa427eef 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -1637,6 +1637,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Pogrešno ste nacrtali uzorak za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Ako napravite još <xliff:g id="NUMBER_1">%2$d</xliff:g> pokušaja bez uspjeha, od vas će se tražiti da otključate telefon pomoću e-pošte. \n\n Pokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> s."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Ukloni"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite li pojačati zvuk iznad preporučenog nivoa?\n\nDužim slušanjem glasnog zvuka možete oštetiti sluh."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li koristiti Prečicu za pristupačnost?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kada je prečica uključena, pritiskom na oba dugmeta za podešavanje jačine zvuka u trajanju od 3 sekunde pokrenut će se funkcija za pristupačnost.\n\n Trenutna funkcija za pristupačnost je:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funkciju možete promijeniti ako odete u Postavke > Pristupačnost."</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 42bebe16a004..930065b02883 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Has dibuixat el patró de desbloqueig <xliff:g id="NUMBER_0">%1$d</xliff:g> vegades de manera incorrecta. Si falles <xliff:g id="NUMBER_1">%2$d</xliff:g> vegades més, se\'t demanarà que desbloquegis el telèfon amb un compte de correu electrònic.\n\n Torna-ho a provar d\'aquí a <xliff:g id="NUMBER_2">%3$d</xliff:g> segons."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Elimina"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vols apujar el volum per sobre del nivell recomanat?\n\nSi escoltes música a un volum alt durant períodes llargs, pots danyar-te l\'oïda."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vols fer servir la drecera d\'accessibilitat?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Si la drecera està activada, prem els dos botons de volum durant 3 segons, per iniciar una funció d\'accessibilitat.\n\n Funció d\'accessibilitat actual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Pots canviar la funció a Configuració > Accessibilitat."</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 2c162914274b..2c017dd52ee1 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1657,6 +1657,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Již <xliff:g id="NUMBER_0">%1$d</xliff:g>krát jste nesprávně nakreslili své heslo odemknutí. Po <xliff:g id="NUMBER_1">%2$d</xliff:g> dalších neúspěšných pokusech budete požádáni o odemčení telefonu pomocí e-mailového účtu.\n\n Zkuste to znovu za <xliff:g id="NUMBER_2">%3$d</xliff:g> s."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Odebrat"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zvýšit hlasitost nad doporučenou úroveň?\n\nDlouhodobý poslech hlasitého zvuku může poškodit sluch."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Použít zkratku přístupnosti?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Když je tato zkratka zapnutá, můžete funkci přístupnosti spustit tím, že na tři sekundy podržíte obě tlačítka hlasitosti.\n\n Aktuální funkce přístupnosti:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funkci můžete změnit v Nastavení > Přístupnost."</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index f86db77184e6..4ebea8947666 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Du har tegnet dit oplåsningsmønster forkert <xliff:g id="NUMBER_0">%1$d</xliff:g> gange. Efter <xliff:g id="NUMBER_1">%2$d</xliff:g> yderligere mislykkede forsøg til vil du blive bedt om at låse din telefon op ved hjælp af en mailkonto.\n\n Prøv igen om <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunder."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Fjern"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vil du skrue højere op end det anbefalede lydstyrkeniveau?\n\nDu kan skade hørelsen ved at lytte til meget høj musik over længere tid."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vil du bruge genvejen til Hjælpefunktioner?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Når genvejen er slået til, kan du starte en hjælpefunktion ved at trykke på begge lydstyrkeknapper i tre sekunder.\n\n Nuværende hjælpefunktion:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Du kan skifte funktion i Indstillinger > Hjælpefunktioner."</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index d6a9a010d9da..6fa0ac1791fa 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Du hast dein Entsperrungsmuster <xliff:g id="NUMBER_0">%1$d</xliff:g>-mal falsch gezeichnet. Nach <xliff:g id="NUMBER_1">%2$d</xliff:g> weiteren erfolglosen Versuchen wirst du aufgefordert, dein Telefon mithilfe eines E-Mail-Kontos zu entsperren.\n\n Versuche es in <xliff:g id="NUMBER_2">%3$d</xliff:g> Sekunden erneut."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Entfernen"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Lautstärke über den Schwellenwert anheben?\n\nWenn du über einen längeren Zeitraum Musik in hoher Lautstärke hörst, kann dies dein Gehör schädigen."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Verknüpfung für Bedienungshilfen verwenden?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Wenn die Verknüpfung aktiviert ist, kannst du die beiden Lautstärketasten drei Sekunden lang gedrückt halten, um eine Bedienungshilfe zu starten.\n\n Aktuelle Bedienungshilfe:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Du kannst die Bedienungshilfe unter \"Einstellungen\" > \"Bedienungshilfen\" ändern."</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 67285e9b0bf3..81a16f28651c 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Σχεδιάσατε το μοτίβο ξεκλειδώματος εσφαλμένα <xliff:g id="NUMBER_0">%1$d</xliff:g> φορές. Μετά από <xliff:g id="NUMBER_1">%2$d</xliff:g> ανεπιτυχείς προσπάθειες ακόμη, θα σας ζητηθεί να ξεκλειδώσετε το τηλέφωνό σας με τη χρήση ενός λογαριασμού ηλεκτρονικού ταχυδρομείου.\n\n Δοκιμάστε ξανά σε <xliff:g id="NUMBER_2">%3$d</xliff:g> δευτερόλεπτα."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Κατάργηση"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Αυξάνετε την ένταση ήχου πάνω από το επίπεδο ασφαλείας;\n\nΑν ακούτε μουσική σε υψηλή ένταση για μεγάλο χρονικό διάστημα ενδέχεται να προκληθεί βλάβη στην ακοή σας."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Να χρησιμοποιείται η συντόμευση προσβασιμότητας;"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Όταν η συντόμευση είναι ενεργοποιημένη, το πάτημα και των δύο κουμπιών έντασης ήχου για 3 δευτερόλεπτα θα ξεκινήσει μια λειτουργία προσβασιμότητας.\n\n Τρέχουσα λειτουργία προσβασιμότητας:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Μπορείτε να αλλάξετε τη λειτουργία από τις Ρυθμίσεις > Προσβασιμότητα."</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index ed31c2274696..87e0c902df8c 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remove"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings > Accessibility."</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 6ae46b4c646e..008ad8aec7fc 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remove"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings > Accessibility."</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index ed31c2274696..87e0c902df8c 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remove"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings > Accessibility."</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index ed31c2274696..87e0c902df8c 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remove"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings > Accessibility."</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 07cb6c7f1c7c..777107a60820 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remove"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature.\n\n Current accessibility feature:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n You can change the feature in Settings > Accessibility."</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 6f83551eca5a..4335c2bf9f27 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Dibujaste incorrectamente tu patrón de desbloqueo <xliff:g id="NUMBER_0">%1$d</xliff:g> veces. Luego de <xliff:g id="NUMBER_1">%2$d</xliff:g> intentos incorrectos más, se te solicitará que desbloquees tu dispositivo mediante el uso de una cuenta de correo.\n\n Vuelve a intentarlo en <xliff:g id="NUMBER_2">%3$d</xliff:g> segundos."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Eliminar"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"¿Quieres subir el volumen por encima del nivel recomendado?\n\nEscuchar a un alto volumen durante largos períodos puede dañar tu audición."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"¿Usar acceso directo de accesibilidad?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Cuando el acceso directo está activado, puedes presionar los botones de volumen durante 3 segundos para iniciar una función de accesibilidad.\n\n Función de accesibilidad actual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Puedes cambiar la función en Configuración > Accesibilidad."</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 737a83b4dbfe..8d750caa6dba 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Has fallado <xliff:g id="NUMBER_0">%1$d</xliff:g> veces al dibujar el patrón de desbloqueo. Si fallas otras <xliff:g id="NUMBER_1">%2$d</xliff:g> veces, deberás usar una cuenta de correo electrónico para desbloquear el teléfono.\n\n Inténtalo de nuevo en <xliff:g id="NUMBER_2">%3$d</xliff:g> segundos."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Quitar"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"¿Quieres subir el volumen por encima del nivel recomendado?\n\nEscuchar sonidos fuertes durante mucho tiempo puede dañar los oídos."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"¿Utilizar acceso directo de accesibilidad?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Si el acceso directo está activado, pulsa los dos botones de volumen durante tres segundos para iniciar una función de accesibilidad.\n\n Función de accesibilidad actual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Puedes cambiar la función en Ajustes > Accesibilidad."</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 021985f50e59..7f423e935b47 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Joonistasite oma avamismustri <xliff:g id="NUMBER_0">%1$d</xliff:g> korda valesti. Pärast veel <xliff:g id="NUMBER_1">%2$d</xliff:g> ebaõnnestunud katset palutakse teil telefon avada meilikontoga.\n\n Proovige uuesti <xliff:g id="NUMBER_2">%3$d</xliff:g> sekundi pärast."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Eemalda"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Kas suurendada helitugevuse taset üle soovitatud taseme?\n\nPikaajaline valju helitugevusega kuulamine võib kuulmist kahjustada."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Kas kasutada juurdepääsetavuse otseteed?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kui otsetee on sisse lülitatud, käivitab mõlema helitugevuse nupu kolm sekundit all hoidmine juurdepääsetavuse funktsiooni.\n\n Praegune juurdepääsetavuse funktsioon:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Saate seda funktsiooni muuta valikutega Seaded > Juurdepääsetavus."</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 8ecdd0765a67..9d014a3b875a 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Desblokeatzeko eredua oker marraztu duzu <xliff:g id="NUMBER_0">%1$d</xliff:g> aldiz. Beste <xliff:g id="NUMBER_1">%2$d</xliff:g> aldiz oker marrazten baduzu, telefonoa posta-kontu baten bidez desblokeatzeko eskatuko dizugu.\n\n Saiatu berriro <xliff:g id="NUMBER_2">%3$d</xliff:g> segundo barru."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Kendu"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Bolumena gomendatutako mailatik gora igo nahi duzu?\n\nMusika bolumen handian eta denbora luzez entzuteak entzumena kalte diezazuke."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Erabilerraztasun-lasterbidea erabili nahi duzu?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Lasterbidea aktibatuta dagoenean, bi bolumen-botoiak hiru segundoz sakatuta abiaraziko da erabilerraztasun-eginbidea.\n\n Uneko erabilerraztasun-eginbidea:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Eginbidea aldatzeko, joan Ezarpenak > Erabilerraztasuna atalera."</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 9c3403b57eac..58ce7a7e547c 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"شما الگوی بازگشایی قفل خود را <xliff:g id="NUMBER_0">%1$d</xliff:g> بار اشتباه کشیدهاید. پس از <xliff:g id="NUMBER_1">%2$d</xliff:g> تلاش ناموفق، از شما خواسته میشود که با استفاده از یک حساب ایمیل قفل تلفن خود را باز کنید.\n\n لطفاً پس از <xliff:g id="NUMBER_2">%3$d</xliff:g> ثانیه دوباره امتحان کنید."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"حذف"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"میزان صدا را به بالاتر از حد توصیه شده افزایش میدهید؟\n\nگوش دادن به صداهای بلند برای مدت طولانی میتواند به شنواییتان آسیب وارد کند."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"از میانبر دسترسپذیری استفاده شود؟"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"وقتی میانبر روشن است، اگر هر دو دکمه صدا را ۳ ثانیه فشار دهید یکی از قابلیتهای دسترسپذیری شروع میشود.\n\n قابلیت دسترسپذیری کنونی:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n میتوانید در «تنظیمات > دسترسپذیری»، قابلیت را تغییر دهید."</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index a42b87d5d7f2..e0f4ed49b6a3 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Piirsit lukituksenpoistokuvion väärin <xliff:g id="NUMBER_0">%1$d</xliff:g> kertaa. Jos piirrät kuvion väärin vielä <xliff:g id="NUMBER_1">%2$d</xliff:g> kertaa, sinua pyydetään poistamaan puhelimesi lukitus sähköpostitilin avulla.\n\n Yritä uudelleen <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunnin kuluttua."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Poista"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Nostetaanko äänenvoimakkuus suositellun tason yläpuolelle?\n\nPitkäkestoinen kova äänenvoimakkuus saattaa heikentää kuuloa."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Käytetäänkö esteettömyyden pikanäppäintä?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kun pikanäppäin on käytössä, voit käynnistää esteettömyystoiminnon pitämällä molempia äänenvoimakkuuspainikkeita painettuna kolmen sekunnin ajan.\n\n Tällä hetkellä valittu esteettömyystoiminto:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Voit vaihtaa toimintoa valitsemalla Asetukset > Esteettömyys."</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 47f41841f747..5d99db170087 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Vous avez dessiné un schéma de déverrouillage incorrect à <xliff:g id="NUMBER_0">%1$d</xliff:g> reprises. Si vous échouez encore <xliff:g id="NUMBER_1">%2$d</xliff:g> fois, vous devrez déverrouiller votre téléphone à l\'aide d\'un compte de messagerie électronique.\n\n Veuillez réessayer dans <xliff:g id="NUMBER_2">%3$d</xliff:g> secondes."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Supprimer"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Augmenter le volume au-dessus du niveau recommandé?\n\nL\'écoute prolongée à un volume élevé peut endommager vos facultés auditives."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utiliser le raccourci d\'accessibilité?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quand le raccourci est activé, appuyez sur les deux boutons de volume pendant trois secondes pour lancer une fonctionnalité d\'accessibilité.\n\n Fonctionnalité d\'accessibilité utilisée actuellement :\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Vous pouvez changer de fonctionnalité sous Paramètres > Accessibilité."</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 7898e797c8e3..3f4f3e6549e4 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Vous avez dessiné un schéma de déverrouillage incorrect à <xliff:g id="NUMBER_0">%1$d</xliff:g> reprises. Si vous échouez encore <xliff:g id="NUMBER_1">%2$d</xliff:g> fois, vous devrez déverrouiller votre téléphone à l\'aide d\'un compte de messagerie électronique.\n\n Veuillez réessayer dans <xliff:g id="NUMBER_2">%3$d</xliff:g> secondes."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Supprimer"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Augmenter le volume au dessus du niveau recommandé ?\n\nL\'écoute prolongée à un volume élevé peut endommager vos facultés auditives."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utiliser le raccourci d\'accessibilité ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quand le raccourci est activé, appuyez sur les deux boutons de volume pendant trois secondes pour lancer une fonctionnalité d\'accessibilité.\n\n Fonctionnalité d\'accessibilité utilisée actuellement :\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Vous pouvez changer de fonctionnalité dans Paramètres > Accessibilité."</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index e7498b954194..2e5052c134b1 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Debuxaches o padrón de desbloqueo incorrectamente <xliff:g id="NUMBER_0">%1$d</xliff:g> veces. Se realizas <xliff:g id="NUMBER_1">%2$d</xliff:g> intentos incorrectos máis, terás que desbloquear o teléfono a través dunha conta de correo electrónico.\n\n Téntao de novo dentro de <xliff:g id="NUMBER_2">%3$d</xliff:g> segundos."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Eliminar"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Queres subir o volume máis do nivel recomendado?\n\nA reprodución de son a un volume elevado durante moito tempo pode provocar danos nos oídos."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Queres utilizar o atallo de accesibilidade?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Cando o atallo está activado, podes premer os dous botóns de volume durante 3 segundos para iniciar unha función de accesibilidade.\n\n Función de accesibilidade actual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Podes cambiar a función en Configuración > Accesibilidade."</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index b52d99ef7b8a..8081ef2709ba 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"તમે તમારી અનલૉક પૅટર્ન <xliff:g id="NUMBER_0">%1$d</xliff:g> વખત ખોટી રીતે દોરી. હજી <xliff:g id="NUMBER_1">%2$d</xliff:g> અસફળ પ્રયાસ પછી, તમને ઇમેઇલ એકાઉન્ટનો ઉપયોગ કરીને ફોનને અનલૉક કરવાનું કહેવામાં આવશે.\n\n<xliff:g id="NUMBER_2">%3$d</xliff:g> સેકન્ડમાં ફરીથી પ્રયાસ કરો."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"દૂર કરો"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ભલામણ કરેલ સ્તરની ઉપર વૉલ્યૂમ વધાર્યો?\n\nલાંબા સમય સુધી ઊંચા અવાજે સાંભળવું તમારી શ્રવણક્ષમતાને નુકસાન પહોંચાડી શકે છે."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ઍક્સેસિબિલિટી શૉર્ટકટનો ઉપયોગ કરીએ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"જ્યારે શૉર્ટકટ ચાલુ હોય, ત્યારે બન્ને વૉલ્યૂમ બટનને 3 સેકન્ડ સુધી દબાવી રાખવાથી ઍક્સેસિબિલિટી સુવિધા શરૂ થઈ જશે.\n\n વર્તમાન ઍક્સેસિબિલિટી સુવિધા:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n તમે સેટિંગ્સ > ઍક્સેસિબિલિટીમાં જઈને આ સુવિધા બદલી શકો છો."</string> @@ -1987,8 +1989,7 @@ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> સ્પ્રેડશીટ"</string> <string name="mime_type_presentation" msgid="1145384236788242075">"પ્રસ્તુતિ"</string> <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> પ્રસ્તુતિ"</string> - <!-- no translation found for bluetooth_airplane_mode_toast (2066399056595768554) --> - <skip /> + <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"એરપ્લેન મોડ દરમિયાન બ્લૂટૂથ ચાલુ રહેશે"</string> <string name="car_loading_profile" msgid="8219978381196748070">"લોડિંગ"</string> <plurals name="file_count" formatted="false" msgid="7063513834724389247"> <item quantity="one"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> ફાઇલ</item> @@ -2009,8 +2010,7 @@ <!-- no translation found for accessibility_system_action_accessibility_menu_label (8436484650391125184) --> <skip /> <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>નું કૅપ્શન બાર."</string> - <!-- no translation found for as_app_forced_to_restricted_bucket (8233871289353898964) --> - <skip /> + <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>ને પ્રતિબંધિત સમૂહમાં મૂકવામાં આવ્યું છે"</string> <!-- no translation found for resolver_personal_tab (2051260504014442073) --> <skip /> <!-- no translation found for resolver_work_tab (2690019516263167035) --> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 3140c76ac43c..05efcd8942cb 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"आपने अपने अनलॉक आकार को <xliff:g id="NUMBER_0">%1$d</xliff:g> बार गलत तरीके से आरेखित किया है. <xliff:g id="NUMBER_1">%2$d</xliff:g> और असफल प्रयासों के बाद, आपसे अपने फ़ोन को किसी ईमेल खाते का उपयोग करके अनलॉक करने के लिए कहा जाएगा.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकंड में फिर से प्रयास करें."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"निकालें"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"वॉल्यूम को सुझाए गए स्तर से ऊपर बढ़ाएं?\n\nअत्यधिक वॉल्यूम पर ज़्यादा समय तक सुनने से आपकी सुनने की क्षमता को नुकसान हो सकता है."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"सुलभता शॉर्टकट का इस्तेमाल करना चाहते हैं?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"इस शॉर्टकट के चालू होने पर, दोनों वॉल्यूम बटनों को 3 सेकंड तक दबाने से सुलभता सुविधा शुरू हो जाएगी.\n\n मौजूदा सुलभता सुविधा:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n आप इस सुविधा को सेटिंग > सुलभता पर जाकर बदल सकते हैं."</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index feb29e22b7ab..e4cefe8149d7 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1635,6 +1635,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Netočno ste iscrtali obrazac za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. Nakon još ovoliko neuspješnih pokušaja: <xliff:g id="NUMBER_1">%2$d</xliff:g> morat ćete otključati telefon pomoću računa e-pošte.\n\n Pokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> s."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Ukloni"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Želite li pojačati zvuk iznad preporučene razine?\n\nDugotrajno slušanje glasne glazbe može vam oštetiti sluh."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite li upotrebljavati prečac za pristupačnost?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kada je taj prečac uključen, pritiskom na obje tipke za glasnoću na 3 sekunde pokrenut će se značajka pristupačnosti.\n\n Trenutačna značajka pristupačnosti:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Značajku možete promijeniti u Postavkama > Pristupačnost."</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 20d38ae44291..08cb1c774b33 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"<xliff:g id="NUMBER_0">%1$d</xliff:g> alkalommal helytelenül rajzolta le a feloldási mintát. További <xliff:g id="NUMBER_1">%2$d</xliff:g> sikertelen kísérlet után egy e-mail fiók használatával kell feloldania a telefonját.\n\n Kérjük, próbálja újra <xliff:g id="NUMBER_2">%3$d</xliff:g> másodperc múlva."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Eltávolítás"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Az ajánlott szint fölé szeretné emelni a hangerőt?\n\nHa hosszú időn át teszi ki magát nagy hangerőnek, azzal károsíthatja a hallását."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Szeretné használni a Kisegítő lehetőségek billentyűparancsot?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Ha be van kapcsolva a billentyűparancs, a két hangerőgomb 3 másodpercig tartó lenyomásával elindíthatja a kisegítő lehetőségek egyik funkcióját.\n\n A kisegítő lehetőségek jelenleg beállított funkciója:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n A funkciót a Beállítások > Kisegítő lehetőségek menüpontban módosíthatja."</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index d13b9ea0770a..9d33f6b8e4fd 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Դուք <xliff:g id="NUMBER_0">%1$d</xliff:g> անգամ սխալ եք հավաքել ձեր ապակողպման նմուշը: <xliff:g id="NUMBER_1">%2$d</xliff:g> անգամից ավել անհաջող փորձերից հետո ձեզ կառաջարկվի ապակողպել ձեր հեռախոսը` օգտագործելով էլփոստի հաշիվ:\n\n Փորձեք կրկին <xliff:g id="NUMBER_2">%3$d</xliff:g> վայրկյանից:"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Հեռացնել"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ձայնը բարձրացնե՞լ խորհուրդ տրվող մակարդակից ավել:\n\nԵրկարատև բարձրաձայն լսելը կարող է վնասել ձեր լսողությունը:"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Օգտագործե՞լ Մատչելիության դյուրանցումը։"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Հատուկ գործառույթն օգտագործելու համար սեղմեք և 3 վայրկյան սեղմած պահեք ձայնի ուժգնության երկու կոճակները, երբ գործառույթը միացված է։\n\n Մատչելիության ակտիվ գործառույթը՝\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Գործառույթը կարող եք փոփոխել՝ անցնելով Կարգավորումներ > Հատուկ գործառույթներ։"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index c2a081757300..48136108d7c1 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Anda telah <xliff:g id="NUMBER_0">%1$d</xliff:g> kali salah menggambar pola pembuka kunci. Setelah <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi upaya gagal, Anda akan diminta membuka kunci ponsel menggunakan akun email.\n\nCoba lagi dalam <xliff:g id="NUMBER_2">%3$d</xliff:g> detik."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Hapus"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Mengeraskan volume di atas tingkat yang disarankan?\n\nMendengarkan dengan volume keras dalam waktu yang lama dapat merusak pendengaran Anda."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gunakan Pintasan Aksesibilitas?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Saat pintasan aktif, menekan kedua tombol volume selama 3 detik akan memulai fitur aksesibilitas.\n\n Fitur aksesibilitas saat ini:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Anda dapat mengubah fitur di Setelan > Aksesibilitas."</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index c596c12205e0..bf3fe87f7931 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Þú hefur teiknað rangt opnunarmynstur <xliff:g id="NUMBER_0">%1$d</xliff:g> sinnum. Eftir <xliff:g id="NUMBER_1">%2$d</xliff:g> árangurslausar tilraunir í viðbót verður þú beðin(n) um að opna símann með tölvupóstreikningi.\n\n Reyndu aftur eftir <xliff:g id="NUMBER_2">%3$d</xliff:g> sekúndur."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Fjarlægja"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Hækka hljóðstyrk umfram ráðlagðan styrk?\n\nEf hlustað er á háum hljóðstyrk í langan tíma kann það að skaða heyrnina."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Viltu nota aðgengisflýtileið?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Þegar flýtileiðin er virk er kveikt á aðgengiseiginleikanum með því að halda báðum hljóðstyrkshnöppunum inni í þrjár sekúndur.\n\n Virkur aðgengiseiginleiki:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Hægt er að skipta um eiginleika í Stillingar > Aðgengi."</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 1144995c069e..72ca138f9ed3 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"<xliff:g id="NUMBER_0">%1$d</xliff:g> tentativi errati di inserimento della sequenza di sblocco. Dopo altri <xliff:g id="NUMBER_1">%2$d</xliff:g> tentativi falliti, ti verrà chiesto di sbloccare il telefono con un account email.\n\n Riprova tra <xliff:g id="NUMBER_2">%3$d</xliff:g> secondi."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Rimuovi"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vuoi aumentare il volume oltre il livello consigliato?\n\nL\'ascolto ad alto volume per lunghi periodi di tempo potrebbe danneggiare l\'udito."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usare la scorciatoia Accessibilità?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quando la scorciatoia è attiva, puoi premere entrambi i pulsanti del volume per tre secondi per avviare una funzione di accessibilità.\n\n Funzione di accessibilità corrente:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Puoi cambiare la funzione in Impostazioni > Accessibilità."</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index bce62ab631da..9c8d0a3a64ec 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1657,6 +1657,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"שרטטת את קו ביטול הנעילה באופן שגוי <xliff:g id="NUMBER_0">%1$d</xliff:g> פעמים. לאחר <xliff:g id="NUMBER_1">%2$d</xliff:g> ניסיונות כושלים נוספים, תתבקש לבטל את נעילת הטלפון באמצעות חשבון אימייל.\n\nנסה שוב בעוד <xliff:g id="NUMBER_2">%3$d</xliff:g> שניות."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"הסר"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"האם להעלות את עוצמת הקול מעל לרמה המומלצת?\n\nהאזנה בעוצמת קול גבוהה למשכי זמן ממושכים עלולה לפגוע בשמיעה."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"להשתמש בקיצור הדרך לתכונת הנגישות?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"כשקיצור הדרך מופעל, לחיצה על שני לחצני עוצמת השמע למשך שלוש שניות מפעילה את תכונת הנגישות.\n\n תכונת הנגישות המוגדרת כרגע:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n אפשר לשנות את התכונה בקטע \'הגדרות ונגישות\'."</string> @@ -2053,8 +2055,7 @@ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"גיליון אלקטרוני <xliff:g id="EXTENSION">%1$s</xliff:g>"</string> <string name="mime_type_presentation" msgid="1145384236788242075">"מצגת"</string> <string name="mime_type_presentation_ext" msgid="8761049335564371468">"מצגת <xliff:g id="EXTENSION">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_airplane_mode_toast (2066399056595768554) --> - <skip /> + <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"Bluetooth יישאר מופעל במהלך מצב טיסה"</string> <string name="car_loading_profile" msgid="8219978381196748070">"בטעינה"</string> <plurals name="file_count" formatted="false" msgid="7063513834724389247"> <item quantity="two"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> קבצים</item> @@ -2077,8 +2078,7 @@ <!-- no translation found for accessibility_system_action_accessibility_menu_label (8436484650391125184) --> <skip /> <string name="accessibility_freeform_caption" msgid="8377519323496290122">"סרגל כיתוב של <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> - <!-- no translation found for as_app_forced_to_restricted_bucket (8233871289353898964) --> - <skip /> + <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> התווספה לקטגוריה \'מוגבל\'"</string> <!-- no translation found for resolver_personal_tab (2051260504014442073) --> <skip /> <!-- no translation found for resolver_work_tab (2690019516263167035) --> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 1ef4a1391860..529d0119f0be 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"ロック解除パターンの入力を<xliff:g id="NUMBER_0">%1$d</xliff:g>回間違えました。あと<xliff:g id="NUMBER_1">%2$d</xliff:g>回間違えると、モバイルデバイスのロック解除にメールアカウントが必要になります。\n\n<xliff:g id="NUMBER_2">%3$d</xliff:g>秒後にもう一度お試しください。"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" - "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"削除"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"推奨レベルを超えるまで音量を上げますか?\n\n大音量で長時間聞き続けると、聴力を損なう恐れがあります。"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ユーザー補助機能のショートカットの使用"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ショートカットが ON の場合、両方の音量ボタンを 3 秒間押し続けるとユーザー補助機能が起動します。\n\n現在のユーザー補助機能:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\nユーザー補助機能は [設定] > [ユーザー補助] で変更できます。"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 41b23887e827..3f97338ade76 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"თქვენ არასწორად დახატეთ თქვენი განბლოკვის ნიმუში <xliff:g id="NUMBER_0">%1$d</xliff:g>-ჯერ. კიდევ <xliff:g id="NUMBER_1">%2$d</xliff:g> წარუმატებელი ცდის შემდეგ, დაგჭირდებათ თქვენი ტელეფონის განბლოკვა ელფოსტის ანგარიშის გამოყენებით.\n\n ხელახლა სცადეთ <xliff:g id="NUMBER_2">%3$d</xliff:g> წამში."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ამოშლა"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"გსურთ ხმის რეკომენდებულ დონეზე მაღლა აწევა?\n\nხანგრძლივად ხმამაღლა მოსმენით შესაძლოა სმენადობა დაიზიანოთ."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"გსურთ მარტივი წვდომის მალსახმობის გამოყენება?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"მალსახმობის ჩართვის შემთხვევაში, ხმის ორივე ღილაკზე 3 წამის განმავლობაში დაჭერით მარტივი წვდომის ფუნქცია ჩაირთვება.\n\n მარტივი წვდომის ამჟამინდელი ფუნქციაა:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ამ ფუნქციის შეცვლა შეგიძლიათ აქ: პარამეტრები > მარტივი წვდომა."</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 3ea4d351edd9..bccd3f4b6a77 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Бекітпені ашу кескінін <xliff:g id="NUMBER_0">%1$d</xliff:g> рет қате сыздыңыз. <xliff:g id="NUMBER_1">%2$d</xliff:g> сәтсіз әрекеттен кейін телефоныңызды есептік жазба арқылы ашу өтінішін аласыз. \n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> секундтан кейін қайта әрекеттеніңіз."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Алып тастау"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дыбыс деңгейін ұсынылған деңгейден көтеру керек пе?\n\nЖоғары дыбыс деңгейінде ұзақ кезеңдер бойы тыңдау есту қабілетіңізге зиян тигізуі мүмкін."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Арнайы мүмкіндік төте жолын пайдалану керек пе?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Бұл төте жол қосулы кезде дыбыс деңгейі түймелерінің екеуін де 3 секунд бойы басқанда арнайы мүмкіндік іске қосылады.\n\n Ағымдағы арнайы мүмкіндік:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Бұл мүмкіндікті \"Параметрлер\" > \"Арнайы мүмкіндіктер\" тармағында өзгертуге болады."</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 4f7c0febb6ce..ee451e936020 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -1615,6 +1615,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"អ្នកបានគូរលំនាំដោះសោរបស់អ្នកមិនត្រឹមត្រូវចំនួន <xliff:g id="NUMBER_0">%1$d</xliff:g> ដង។ បន្ទាប់ពីការព្យាយាមមិនជោគជ័យច្រើនជាង <xliff:g id="NUMBER_1">%2$d</xliff:g> ដង អ្នកនឹងត្រូវបានស្នើឲ្យដោះសោទូរស័ព្ទរបស់អ្នកដោយប្រើគណនីអ៊ីមែល។\n\n ព្យាយាមម្ដងទៀតក្នុងរយៈពេល <xliff:g id="NUMBER_2">%3$d</xliff:g> វិនាទី។"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"លុបចេញ"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"បង្កើនកម្រិតសំឡេងលើសពីកម្រិតបានផ្ដល់យោបល់?\n\nការស្ដាប់នៅកម្រិតសំឡេងខ្លាំងយូរអាចធ្វើឲ្យខូចត្រចៀក។"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ប្រើប្រាស់ផ្លូវកាត់ភាពងាយស្រួល?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"នៅពេលផ្លូវកាត់នេះបើក ការចុចប៊ូតុងកម្រិតសំឡេងទាំងពីរឲ្យជាប់រយៈពេល 3 វិនាទីនឹងចាប់ផ្តើមមុខងារភាពងាយស្រួល។\n\n មុខងារភាពងាយស្រួលបច្ចុប្បន្ន៖\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n អ្នកអាចផ្លាស់ប្តូរមុខងារនេះបាននៅក្នុងការ កំណត់ > ភាពងាយស្រួល។"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 9efa85774bab..75282842175c 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"ನಿಮ್ಮ ಅನ್ಲಾಕ್ ಪ್ಯಾಟರ್ನ್ ಅನ್ನು ನೀವು <xliff:g id="NUMBER_0">%1$d</xliff:g> ಬಾರಿ ತಪ್ಪಾಗಿ ಡ್ರಾ ಮಾಡಿರುವಿರಿ. <xliff:g id="NUMBER_1">%2$d</xliff:g> ಹೆಚ್ಚಿನ ವಿಫಲ ಪ್ರಯತ್ನಗಳ ಬಳಿಕ, ನಿಮ್ಮ ಇಮೇಲ್ ಖಾತೆಯನ್ನು ಬಳಸಿಕೊಂಡು ನಿಮ್ಮ ಫೋನ್ ಅನ್ಲಾಕ್ ಮಾಡುವಂತೆ ನಿಮ್ಮಲ್ಲಿ ಕೇಳಿಕೊಳ್ಳಲಾಗುತ್ತದೆ.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ಸೆಕೆಂಡುಗಳಲ್ಲಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ತೆಗೆದುಹಾಕು"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ವಾಲ್ಯೂಮ್ ಅನ್ನು ಶಿಫಾರಸು ಮಾಡಲಾದ ಮಟ್ಟಕ್ಕಿಂತಲೂ ಹೆಚ್ಚು ಮಾಡುವುದೇ?\n\nದೀರ್ಘ ಅವಧಿಯವರೆಗೆ ಹೆಚ್ಚಿನ ವಾಲ್ಯೂಮ್ನಲ್ಲಿ ಆಲಿಸುವುದರಿಂದ ನಿಮ್ಮ ಆಲಿಸುವಿಕೆ ಸಾಮರ್ಥ್ಯಕ್ಕೆ ಹಾನಿಯುಂಟು ಮಾಡಬಹುದು."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ಪ್ರವೇಶಿಸುವಿಕೆ ಶಾರ್ಟ್ಕಟ್ ಬಳಸುವುದೇ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ಶಾರ್ಟ್ಕಟ್ ಆನ್ ಆಗಿರುವಾಗ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯ ಆನ್ ಮಾಡಲು, ಎರಡೂ ವಾಲ್ಯೂಮ್ ಬಟನ್ಗಳನ್ನು ನೀವು 3 ಸೆಕೆಂಡುಗಳ ಕಾಲ ಒತ್ತಬೇಕು.\n\nಪ್ರಸ್ತುತ ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯ: \n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ಸೆಟ್ಟಿಂಗ್ಗಳು ಮತ್ತು ಪ್ರವೇಶಿಸುವಿಕೆಯಲ್ಲಿ ನೀವು ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬದಲಾಯಿಸಬಹುದು."</string> @@ -1987,8 +1989,7 @@ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> ಸ್ಪ್ರೆಡ್ಶೀಟ್"</string> <string name="mime_type_presentation" msgid="1145384236788242075">"ಪ್ರಸ್ತುತಿ"</string> <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> ಪ್ರಸ್ತುತಿ"</string> - <!-- no translation found for bluetooth_airplane_mode_toast (2066399056595768554) --> - <skip /> + <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"ಏರ್ಪ್ಲೇನ್ ಮೋಡ್ನಲ್ಲಿರುವಾಗಲೂ ಬ್ಲೂಟೂತ್ ಆನ್ ಆಗಿರುತ್ತದೆ"</string> <string name="car_loading_profile" msgid="8219978381196748070">"ಲೋಡ್ ಆಗುತ್ತಿದೆ"</string> <plurals name="file_count" formatted="false" msgid="7063513834724389247"> <item quantity="one"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> ಫೈಲ್ಗಳು</item> @@ -2009,8 +2010,7 @@ <!-- no translation found for accessibility_system_action_accessibility_menu_label (8436484650391125184) --> <skip /> <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಆ್ಯಪ್ನ ಶೀರ್ಷಿಕೆಯ ಪಟ್ಟಿ."</string> - <!-- no translation found for as_app_forced_to_restricted_bucket (8233871289353898964) --> - <skip /> + <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> ಅನ್ನು ನಿರ್ಬಂಧಿತ ಬಕೆಟ್ಗೆ ಹಾಕಲಾಗಿದೆ"</string> <!-- no translation found for resolver_personal_tab (2051260504014442073) --> <skip /> <!-- no translation found for resolver_work_tab (2690019516263167035) --> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 90fa0fa65f1d..d5b450757bf9 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"잠금해제 패턴을 <xliff:g id="NUMBER_0">%1$d</xliff:g>회 잘못 그렸습니다. <xliff:g id="NUMBER_1">%2$d</xliff:g>회 더 실패하면 이메일 계정을 사용하여 휴대전화를 잠금해제해야 합니다.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g>초 후에 다시 시도해 주세요."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"삭제"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"권장 수준 이상으로 볼륨을 높이시겠습니까?\n\n높은 볼륨으로 장시간 청취하면 청력에 손상이 올 수 있습니다."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"접근성 단축키를 사용하시겠습니까?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"단축키가 사용 설정된 경우 두 개의 볼륨 버튼을 3초간 누르면 접근성 기능이 시작됩니다.\n\n 현재 접근성 기능:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n \'설정 > 접근성\'에서 기능을 변경할 수 있습니다."</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 07a6e3f75222..533e0b816142 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -540,7 +540,7 @@ <string name="fingerprint_error_timeout" msgid="2946635815726054226">"Манжа изин күтүү мөөнөтү бүттү. Кайра аракет кылыңыз."</string> <string name="fingerprint_error_canceled" msgid="540026881380070750">"Манжа изи иш-аракети жокко чыгарылды."</string> <string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Манжа изи операциясын колдонуучу жокко чыгарды."</string> - <string name="fingerprint_error_lockout" msgid="7853461265604738671">"Аракеттер өтө көп болду. Кийинчерээк кайра аракет кылыңыз."</string> + <string name="fingerprint_error_lockout" msgid="7853461265604738671">"Аракеттер өтө көп болду. Бир аздан кийин кайталап көрүңүз."</string> <string name="fingerprint_error_lockout_permanent" msgid="3895478283943513746">"Өтө көп жолу аракет жасадыңыз. Манжа изинин сенсору өчүрүлдү."</string> <string name="fingerprint_error_unable_to_process" msgid="1148553603490048742">"Кайра бир аракеттениңиз."</string> <string name="fingerprint_error_no_fingerprints" msgid="8671811719699072411">"Бир да манжа изи катталган эмес."</string> @@ -583,7 +583,7 @@ <string name="face_error_no_space" msgid="5649264057026021723">"Жаңы жүздү сактоо мүмкүн эмес. Адегенде эскисин өчүрүңүз."</string> <string name="face_error_canceled" msgid="2164434737103802131">"Жүздүн аныктыгын текшерүү жокко чыгарылды."</string> <string name="face_error_user_canceled" msgid="8553045452825849843">"Жүзүнөн таануу функциясын колдонуучу өчүрүп салды."</string> - <string name="face_error_lockout" msgid="7864408714994529437">"Өтө көп жолу аракет жасадыңыз. Кийинчерээк кайра аракет кылыңыз."</string> + <string name="face_error_lockout" msgid="7864408714994529437">"Өтө көп жолу аракет жасадыңыз. Бир аздан кийин кайталап көрүңүз."</string> <string name="face_error_lockout_permanent" msgid="8277853602168960343">"Өтө көп жолу аракет кылдыңыз. Жүзүнөн таануу функциясы өчүрүлдү."</string> <string name="face_error_unable_to_process" msgid="5723292697366130070">"Жүз ырасталбай жатат. Кайра аракет кылыңыз."</string> <string name="face_error_not_enrolled" msgid="7369928733504691611">"Жүзүнөн таануу функциясын жөндөй элексиз."</string> @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Графикалык ачкычты <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес көрсөттүңүз. <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу туура эмес көрсөтүлгөндөн кийин, телефондун кулпусун ачуу үчүн Google аккаунтуңузга кирүүгө туура келет.\n\n<xliff:g id="NUMBER_2">%3$d</xliff:g> секундадан кийин кайталап көрсөңүз болот."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Алып салуу"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Сунушталган деңгээлден да катуулатып уккуңуз келеби?\n\nМузыканы узакка чейин катуу уксаңыз, угууңуз начарлап кетиши мүмкүн."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ыкчам иштетесизби?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Атайын мүмкүнчүлүктөр функциясын пайдалануу үчүн, ал күйгүзүлгөндө, үндү катуулатып/акырындаткан эки баскычты тең үч секунддай кое бербей басып туруңуз.\n\n Учурдагы атайын мүмкүнчүлүктөрдүн жөндөөлөрү:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\nЖөндөөлөр > Атайын мүмкүнчүлүктөр бөлүмүнөн өзгөртө аласыз."</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index 05f9698cd48d..7a3e85e7f5b2 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"ທ່ານແຕ້ມຮູບແບບປົດລັອກຂອງທ່ານຜິດ <xliff:g id="NUMBER_0">%1$d</xliff:g> ເທື່ອແລ້ວ. ຫຼັງຈາກຄວາມພະຍາຍາມອີກ <xliff:g id="NUMBER_1">%2$d</xliff:g> ເທື່ອ ທ່ານຈະຖືກຖາມໃຫ້ປົດລັອກໂທລະສັບຂອງທ່ານດ້ວຍບັນຊີອີເມວ.\n\n ລອງໃໝ່ອີກຄັ້ງໃນ <xliff:g id="NUMBER_2">%3$d</xliff:g> ວິນາທີ."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ລຶບອອກ"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ເພີ່ມລະດັບສຽງໃຫ້ເກີນກວ່າລະດັບທີ່ແນະນຳບໍ?\n\nການຮັບຟັງສຽງໃນລະດັບທີ່ສູງເປັນໄລຍະເວລາດົນອາດເຮັດໃຫ້ການຟັງຂອງທ່ານມີບັນຫາໄດ້."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ໃຊ້ປຸ່ມລັດການຊ່ວຍເຂົ້າເຖິງບໍ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ເມື່ອເປີດໃຊ້ປຸ່ມລັດແລ້ວ, ໃຫ້ກົດປຸ່ມສຽງທັງສອງຄ້າງໄວ້ 3 ວິນາທີເພື່ອເລີ່ມຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ.\n\n ຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງປັດຈຸບັນ:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ທ່ານສາມາດປ່ຽນຄຸນສົມບັດໄດ້ໃນການຕັ້ງຄ່າ > ການຊ່ວຍເຂົ້າເຖິງ."</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 4f48685f2a3b..265c97e4b228 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1657,6 +1657,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Netinkamai nupiešėte atrakinimo piešinį <xliff:g id="NUMBER_0">%1$d</xliff:g> k. Po dar <xliff:g id="NUMBER_1">%2$d</xliff:g> nesėkm. band. būsite paprašyti atrakinti telefoną naudodami „Google“ prisijungimo duomenis.\n\n Bandykite dar kartą po <xliff:g id="NUMBER_2">%3$d</xliff:g> sek."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Pašalinti"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Padidinti garsą daugiau nei rekomenduojamas lygis?\n\nIlgai klausydami dideliu garsu galite pažeisti klausą."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Naudoti spartųjį pritaikymo neįgaliesiems klavišą?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kai spartusis klavišas įjungtas, spaudžiant abu garsumo mygtukus 3 sekundes bus paleista pritaikymo neįgaliesiems funkcija.\n\n Dabartinė pritaikymo neįgaliesiems funkcija:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>„\n“\n Funkciją galite pakeisti skiltyje „Nustatymai“ > „Pritaikymas neįgaliesiems“."</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index ae9abc6331a3..a535f6dd9feb 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1635,6 +1635,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Jūs nepareizi norādījāt atbloķēšanas kombināciju <xliff:g id="NUMBER_0">%1$d</xliff:g> reizes. Pēc vēl <xliff:g id="NUMBER_1">%2$d</xliff:g> neveiksmīgiem mēģinājumiem tālrunis būs jāatbloķē, izmantojot e-pasta kontu.\n\nMēģiniet vēlreiz pēc <xliff:g id="NUMBER_2">%3$d</xliff:g> sekundēm."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Noņemt"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vai palielināt skaļumu virs ieteicamā līmeņa?\n\nIlgstoši klausoties skaņu lielā skaļumā, var tikt bojāta dzirde."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vai izmantot pieejamības saīsni?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Ja saīsne ir iespējota, vienlaikus nospiežot abas skaļuma regulēšanas pogas un trīs sekundes turot tās, tiks palaista pieejamības funkcija.\n\n Pašreiz iestatītā pieejamības funkcija:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Šo funkciju var mainīt sadaļā Iestatījumi > Pieejamība."</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index f1e54064036f..2f75269cf421 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -1615,6 +1615,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Погрешно сте ја употребиле вашата шема на отклучување <xliff:g id="NUMBER_0">%1$d</xliff:g> пати. По <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешни обиди, ќе побараат од вас да го отклучите телефонот со користење сметка на е-пошта.\n\n Обидете се повторно за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунди."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Отстрани"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Да го зголемиме звукот над препорачаното ниво?\n\nСлушањето звуци со голема јачина подолги периоди може да ви го оштети сетилото за слух."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Да се користи кратенка за „Пристапност“?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Кога е вклучена кратенката, ако ги притиснете двете копчиња за јачина на звук во времетраење од 3 секунди, ќе се стартува функција на пристапност.\n\n Тековна функција на пристапност:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Функцијата може да ја промените во „Поставки“ > „Пристапност“."</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index f0cc48cf9bd9..25d72126418f 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"നിങ്ങളുടെ അൺലോക്ക് പാറ്റേൺ <xliff:g id="NUMBER_0">%1$d</xliff:g> തവണ തെറ്റായി വരച്ചു. <xliff:g id="NUMBER_1">%2$d</xliff:g> ശ്രമങ്ങൾ കൂടി വിജയിച്ചില്ലെങ്കിൽ, ഒരു ഇമെയിൽ അക്കൗണ്ട് ഉപയോഗിച്ച് ഫോൺ അൺലോക്ക് ചെയ്യാൻ നിങ്ങളോട് ആവശ്യപ്പെടും.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> സെക്കൻഡിനുള്ള വീണ്ടും ശ്രമിക്കുക."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"നീക്കംചെയ്യുക"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"മുകളിൽക്കൊടുത്തിരിക്കുന്ന ശുപാർശചെയ്ത ലെവലിലേക്ക് വോളിയം വർദ്ധിപ്പിക്കണോ?\n\nഉയർന്ന വോളിയത്തിൽ ദീർഘനേരം കേൾക്കുന്നത് നിങ്ങളുടെ ശ്രവണ ശേഷിയെ ദോഷകരമായി ബാധിക്കാം."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ഉപയോഗസഹായി കുറുക്കുവഴി ഉപയോഗിക്കണോ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"കുറുക്കുവഴി ഓണാണെങ്കിൽ, രണ്ട് വോളിയം ബട്ടണുകളും 3 സെക്കൻഡ് നേരത്തേക്ക് അമർത്തുന്നത് ഉപയോഗസഹായി ഫീച്ചർ ആരംഭിക്കും.\n\n നിലവിലെ ഉപയോഗസഹായി ഫീച്ചർ:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ക്രമീകരണം > ഉപയോഗസഹായി എന്നതിൽ ഏത് സമയത്തും നിങ്ങൾക്ക് ഫീച്ചർ മാറ്റാവുന്നതാണ്."</string> @@ -1987,8 +1989,7 @@ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> സ്പ്രെഡ്ഷീറ്റ്"</string> <string name="mime_type_presentation" msgid="1145384236788242075">"അവതരണം"</string> <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> അവതരണം"</string> - <!-- no translation found for bluetooth_airplane_mode_toast (2066399056595768554) --> - <skip /> + <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"ഫ്ലൈറ്റ് മോഡ് ഓണാക്കിയിരിക്കുമ്പോഴും Bluetooth ലഭ്യമാകും"</string> <string name="car_loading_profile" msgid="8219978381196748070">"ലോഡ് ചെയ്യുന്നു"</string> <plurals name="file_count" formatted="false" msgid="7063513834724389247"> <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> ഫയലുകൾ</item> @@ -2009,8 +2010,7 @@ <!-- no translation found for accessibility_system_action_accessibility_menu_label (8436484650391125184) --> <skip /> <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിന്റെ അടിക്കുറിപ്പ് ബാർ."</string> - <!-- no translation found for as_app_forced_to_restricted_bucket (8233871289353898964) --> - <skip /> + <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> നിയന്ത്രിത ബക്കറ്റിലേക്ക് നീക്കി"</string> <!-- no translation found for resolver_personal_tab (2051260504014442073) --> <skip /> <!-- no translation found for resolver_work_tab (2690019516263167035) --> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index 3be3a0442401..3acdb7245b7c 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Та тайлах хээг <xliff:g id="NUMBER_0">%1$d</xliff:g> удаа буруу зурлаа. <xliff:g id="NUMBER_1">%2$d</xliff:g> удаа дахин буруу оруулбал, та утсаа тайлахын тулд имэйл бүртгэлээ ашиглах шаардлагатай болно.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> секундын дараа дахин оролдоно уу."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Устгах"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Дууг санал болгосноос чанга болгож өсгөх үү?\n\nУрт хугацаанд чанга хөгжим сонсох нь таны сонсголыг муутгаж болно."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Хүртээмжийн товчлолыг ашиглах уу?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Товчлолыг асаасан үед дуун товчлуурыг 3 секунд дарснаар хүртээмжийн онцлогийг эхлүүлнэ.\n\n Одоогийн хүртээмжийн онцлог:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Онцлогийг Тохиргоо > Хүртээмж хэсэгт өөрчлөх боломжтой."</string> @@ -1987,8 +1989,7 @@ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g>-н хүснэгт"</string> <string name="mime_type_presentation" msgid="1145384236788242075">"Үзүүлэн"</string> <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g>-н үзүүлэн"</string> - <!-- no translation found for bluetooth_airplane_mode_toast (2066399056595768554) --> - <skip /> + <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"Нислэгийн горимын үеэр Bluetooth асаалттай байх болно"</string> <string name="car_loading_profile" msgid="8219978381196748070">"Ачаалж байна"</string> <plurals name="file_count" formatted="false" msgid="7063513834724389247"> <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> файл</item> @@ -2009,8 +2010,7 @@ <!-- no translation found for accessibility_system_action_accessibility_menu_label (8436484650391125184) --> <skip /> <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>-н гарчгийн талбар."</string> - <!-- no translation found for as_app_forced_to_restricted_bucket (8233871289353898964) --> - <skip /> + <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g>-г ХЯЗГААРЛАСАН сагс руу орууллаа"</string> <!-- no translation found for resolver_personal_tab (2051260504014442073) --> <skip /> <!-- no translation found for resolver_work_tab (2690019516263167035) --> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index a2bf4796eaec..ff9f3e741c85 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"तुम्ही तुमचा अनलॉक पॅटर्न <xliff:g id="NUMBER_0">%1$d</xliff:g> वेळा अयोग्यपणे काढला आहे. आणखी <xliff:g id="NUMBER_1">%2$d</xliff:g> अयशस्वी प्रयत्नांनंतर, तुम्हाला ईमेल खाते वापरून तुमचा फोन अनलॉक करण्यास सांगितले जाईल.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकंदांमध्ये पुन्हा प्रयत्न करा."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"काढा"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"शिफारस केलेल्या पातळीच्या वर आवाज वाढवायचा?\n\nउच्च आवाजात दीर्घ काळ ऐकण्याने आपल्या श्रवणशक्तीची हानी होऊ शकते."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"प्रवेशयोग्यता शॉर्टकट वापरायचा?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"शॉर्टकट चालू असताना, दोन्ही आवाज बटणे 3 सेकंद दाबल्याने प्रवेशयोग्यता वैशिष्ट्य सुरू होईल.\n\n वर्तमान प्रवेशयोग्यता वैशिष्ट्य:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n तुम्ही सेटिंग्ज > प्रवेशयोग्यता मध्ये वैशिष्ट्य बदलू शकता."</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index f7626dbe1935..e4364083751d 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Anda telah tersilap lukis corak buka kunci sebanyak <xliff:g id="NUMBER_0">%1$d</xliff:g> kali. Selepas <xliff:g id="NUMBER_1">%2$d</xliff:g> lagi percubaan yang tidak berjaya, anda akan diminta membuka kunci telefon anda menggunakan log masuk Google anda.\n\n Cuba lagi dalam <xliff:g id="NUMBER_2">%3$d</xliff:g> saat."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Alih keluar"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Naikkan kelantangan melebihi paras yang disyokorkan?\n\nMendengar pada kelantangan yang tinggi untuk tempoh yang lama boleh merosakkan pendengaran anda."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gunakan Pintasan Kebolehaksesan?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Apabila pintasan dihidupkan, tindakan menekan kedua-dua butang kelantangan selama 3 saat akan memulakan ciri kebolehaksesan.\n\n Ciri kebolehaksesan semasa:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Anda boleh menukar ciri itu dalam Tetapan > Kebolehaksesan."</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 556837efa8a3..56f49c28b26d 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"သင် ပုံဖော်၍သော့ဖွင့်ခြင်းကို <xliff:g id="NUMBER_0">%1$d</xliff:g> အကြိမ် မှန်ကန်စွာ မပြုလုပ်နိုင်ပါ။ နောက်ထပ် <xliff:g id="NUMBER_1">%2$d</xliff:g> အကြိမ် မမှန်ကန်ပါက သင့်ဖုန်းအား အီးမေးလ်အသုံးပြု၍ သော့ဖွင့်ရန် တောင်းဆိုပါလိမ့်မည်။ \n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> စက္ကန့်အကြာတွင် ပြန်လည် ကြိုးစားပါ"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ဖယ်ရှားရန်"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"အသံကို အကြံပြုထားသည့် ပမာဏထက် မြှင့်ပေးရမလား?\n\nအသံကို မြင့်သည့် အဆင့်မှာ ကြာရှည်စွာ နားထောင်ခြင်းက သင်၏ နားကို ထိခိုက်စေနိုင်သည်။"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"အများသုံးစွဲနိုင်မှု ဖြတ်လမ်းလင့်ခ်ကို အသုံးပြုလိုပါသလား။"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ဖြတ်လမ်းလင့်ခ်ကို ဖွင့်ထားစဉ် အသံအတိုးအလျှော့ခလုတ် နှစ်ခုစလုံးကို ၃ စက္ကန့်ခန့် ဖိထားခြင်းဖြင့် အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုကို ဖွင့်နိုင်သည်။\n\n လက်ရှိ အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှု−\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ဝန်ဆောင်မှုကို ဆက်တင်များ > အများသုံးစွဲနိုင်မှုတွင် ပြောင်းလဲနိုင်ပါသည်။"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 6f3b6c213cad..bb3d52f7b854 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Du har tegnet opplåsningsmønsteret feil <xliff:g id="NUMBER_0">%1$d</xliff:g> ganger. Etter ytterligere <xliff:g id="NUMBER_1">%2$d</xliff:g> gale forsøk, blir du bedt om å låse opp telefonen via en e-postkonto.\n\n Prøv på nytt om <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunder."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Fjern"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vil du øke volumet til over anbefalt nivå?\n\nHvis du hører på et høyt volum over lengre perioder, kan det skade hørselen din."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vil du bruke tilgjengelighetssnarveien?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Når snarveien er på, starter en tilgjengelighetsfunksjon når du trykker inn begge volumknappene i tre sekunder.\n\n Nåværende tilgjengelighetsfunksjon:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Du kan endre funksjonen i Innstillinger > Tilgjengelighet."</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index ebbf50bf41e4..b3d6cdecf8ed 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -1619,6 +1619,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"तपाईँले आफ्नो अनलक ढाँचा गलत रूपमा <xliff:g id="NUMBER_0">%1$d</xliff:g> पटक तान्नु भएको छ। <xliff:g id="NUMBER_1">%2$d</xliff:g> धेरै असफल प्रयासहरूपछि, तपाईँलाई एउटा इमेल खाताको प्रयोग गरेर तपाईँको फोन अनलक गर्न सोधिने छ।\n\n फेरि <xliff:g id="NUMBER_2">%3$d</xliff:g> सेकेन्डमा प्रयास गर्नुहोस्।"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"हटाउनुहोस्"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"सिफारिस तहभन्दा आवाज ठुलो गर्नुहुन्छ?\n\nलामो समय सम्म उच्च आवाजमा सुन्दा तपाईँको सुन्ने शक्तिलाई हानी गर्न सक्छ।"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"पहुँच सम्बन्धी सर्टकट प्रयोग गर्ने हो?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"सर्टकट सक्रिय हुँदा, भोल्युमका दुवै बटनहरूलाई ३ सेकेन्डसम्म थिची राख्नाले पहुँच सम्बन्धी कुनै सुविधा सुरु हुनेछ।\n\n हाल व्यवहारमा रहेको पहुँच सम्बन्धी सुविधा:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n तपाईं सेटिङहरू अन्तर्गतको पहुँच सम्बन्धी विकल्पमा गई उक्त सुविधालाई बदल्न सक्नुहुन्छ।"</string> @@ -1993,8 +1995,7 @@ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> स्प्रेडसिट"</string> <string name="mime_type_presentation" msgid="1145384236788242075">"प्रस्तुति"</string> <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> प्रस्तुति"</string> - <!-- no translation found for bluetooth_airplane_mode_toast (2066399056595768554) --> - <skip /> + <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"हवाइजहाज मोडमा ब्लुटुथ सक्रिय रहने छ"</string> <string name="car_loading_profile" msgid="8219978381196748070">"लोड गर्दै"</string> <plurals name="file_count" formatted="false" msgid="7063513834724389247"> <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> फाइलहरू</item> @@ -2015,8 +2016,7 @@ <!-- no translation found for accessibility_system_action_accessibility_menu_label (8436484650391125184) --> <skip /> <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> को क्याप्सन बार।"</string> - <!-- no translation found for as_app_forced_to_restricted_bucket (8233871289353898964) --> - <skip /> + <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> लाई प्रतिबन्धित बाल्टीमा राखियो"</string> <!-- no translation found for resolver_personal_tab (2051260504014442073) --> <skip /> <!-- no translation found for resolver_work_tab (2690019516263167035) --> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 0635c214a4c1..47de950c5c7d 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Je hebt je ontgrendelingspatroon <xliff:g id="NUMBER_0">%1$d</xliff:g> keer onjuist getekend. Na nog eens <xliff:g id="NUMBER_1">%2$d</xliff:g> mislukte pogingen wordt u gevraagd je telefoon te ontgrendelen via een e-mailaccount.\n\n Probeer het over <xliff:g id="NUMBER_2">%3$d</xliff:g> seconden opnieuw."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Verwijderen"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Volume verhogen tot boven het aanbevolen niveau?\n\nAls je langere tijd op hoog volume naar muziek luistert, raakt je gehoor mogelijk beschadigd."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Snelkoppeling toegankelijkheid gebruiken?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Wanneer de snelkoppeling is ingeschakeld, kun je drie seconden op beide volumeknoppen drukken om een toegankelijkheidsfunctie te starten.\n\n Huidige toegankelijkheidsfunctie:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Je kunt de functie wijzigen in Instellingen > Toegankelijkheid."</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 03a7ea3be704..1242188f791a 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"ଆପଣଙ୍କ ଅନଲକ୍ ପାଟର୍ନକୁ ଆପଣ <xliff:g id="NUMBER_0">%1$d</xliff:g> ଥର ଭୁଲ ଭାବେ ଅଙ୍କନ କରିଛନ୍ତି। ଆଉ <xliff:g id="NUMBER_1">%2$d</xliff:g>ଟି ଭୁଲ ପ୍ରୟାସ ପରେ ଏକ ଇମେଲ୍ ଆକାଉଣ୍ଟ ବ୍ୟବହାର କରି ନିଜ ଫୋନ୍କୁ ଅନଲକ୍ କରିବା ପାଇଁ କୁହାଯିବ।\n\n<xliff:g id="NUMBER_2">%3$d</xliff:g> ସେକେଣ୍ଡ ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ବାହାର କରନ୍ତୁ"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ମାତ୍ରା ବଢ଼ାଇ ସୁପାରିଶ ସ୍ତର ବଢ଼ାଉଛନ୍ତି? \n\n ଲମ୍ବା ସମୟ ପର୍ଯ୍ୟନ୍ତ ଉଚ୍ଚ ଶବ୍ଦରେ ଶୁଣିଲେ ଆପଣଙ୍କ ଶ୍ରବଣ ଶକ୍ତି ଖରାପ ହୋଇପାରେ।"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ଆକ୍ସେସବିଲିଟି ଶର୍ଟକଟ୍ ବ୍ୟବହାର କରିବେ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ସର୍ଟକଟ୍ ଅନ୍ ଥିବା ବେଳେ, ଉଭୟ ଭଲ୍ୟୁମ୍ ବଟନ୍ 3 ସେକେଣ୍ଡ ପାଇଁ ଦବାଇବା ଦ୍ୱାରା ଆକ୍ସେସବିଲିଟି ବୈଶିଷ୍ଟ ଆରମ୍ଭ ହେବ।\n\n ସମ୍ପ୍ରତି ଆକ୍ସେସବିଲିଟି ବୈଶିଷ୍ଟ୍ୟ:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ସେଟିଙ୍ଗ ଓ ଆକ୍ସେସବିଲିଟିରେ ଆପଣ ବୈଶିଷ୍ଟ୍ୟ ବଦଳାଇ ପାରିବେ।"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index b2cca4db6615..b6513c399559 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"ਤੁਸੀਂ <xliff:g id="NUMBER_0">%1$d</xliff:g> ਵਾਰ ਆਪਣਾ ਅਣਲਾਕ ਪੈਟਰਨ ਗਲਤ ਢੰਗ ਨਾਲ ਡ੍ਰਾ ਕੀਤਾ ਹੈ। <xliff:g id="NUMBER_1">%2$d</xliff:g> ਹੋਰ ਅਸਫਲ ਕੋਸ਼ਿਸ਼ਾਂ ਤੋਂ ਬਾਅਦ, ਤੁਹਾਨੂੰ ਇੱਕ ਈਮੇਲ ਖਾਤਾ ਵਰਤਦੇ ਹੋਏ ਆਪਣਾ ਫ਼ੋਨ ਅਣਲਾਕ ਕਰਨ ਲਈ ਕਿਹਾ ਜਾਏਗਾ।\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> ਸਕਿੰਟਾਂ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ਹਟਾਓ"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"ਕੀ ਵੌਲਿਊਮ ਸਿਫ਼ਾਰਸ਼ ਕੀਤੇ ਪੱਧਰ ਤੋਂ ਵਧਾਉਣੀ ਹੈ?\n\nਲੰਮੇ ਸਮੇਂ ਤੱਕ ਉੱਚ ਵੌਲਿਊਮ ਤੇ ਸੁਣਨ ਨਾਲ ਤੁਹਾਡੀ ਸੁਣਨ ਸ਼ਕਤੀ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚ ਸਕਦਾ ਹੈ।"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ਕੀ ਪਹੁੰਚਯੋਗਤਾ ਸ਼ਾਰਟਕੱਟ ਵਰਤਣਾ ਹੈ?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ਸ਼ਾਰਟਕੱਟ ਚਾਲੂ ਹੋਣ \'ਤੇ, ਕਿਸੇ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਅਵਾਜ਼ ਬਟਨਾਂ ਨੂੰ 3 ਸਕਿੰਟ ਲਈ ਦਬਾ ਕੇ ਰੱਖੋ।\n\n ਵਰਤਮਾਨ ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾ:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n ਤੁਸੀਂ ਸੈਟਿੰਗਾਂ > ਪਹੁੰਚਯੋਗਤਾ ਵਿੱਚ ਵਿਸ਼ੇਸ਼ਤਾ ਨੂੰ ਬਦਲ ਸਕਦੇ ਹੋ।"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 94fd851f7c66..964c973e491f 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1657,6 +1657,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Po raz <xliff:g id="NUMBER_0">%1$d</xliff:g> nieprawidłowo narysowałeś wzór odblokowania. Po kolejnych <xliff:g id="NUMBER_1">%2$d</xliff:g> nieudanych próbach konieczne będzie odblokowanie telefonu przy użyciu danych logowania na konto Google.\n\n Spróbuj ponownie za <xliff:g id="NUMBER_2">%3$d</xliff:g> s."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Usuń"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zwiększyć głośność ponad zalecany poziom?\n\nSłuchanie głośno przez długi czas może uszkodzić Twój słuch."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Użyć skrótu do ułatwień dostępu?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Gdy skrót jest włączony, jednoczesne naciśnięcie przez trzy sekundy obu klawiszy sterowania głośnością uruchomi funkcję ułatwień dostępu.\n\nBieżąca funkcja ułatwień dostępu:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\nFunkcję możesz zmienić, wybierając Ustawienia > Ułatwienia dostępu."</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 72a3ce904a8c..a6fd0fe230ec 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Você desenhou sua sequência de desbloqueio incorretamente <xliff:g id="NUMBER_0">%1$d</xliff:g> vezes. Se fizer mais <xliff:g id="NUMBER_1">%2$d</xliff:g> tentativas incorretas, será solicitado que você use o login do Google para desbloquear.\n\n Tente novamente em <xliff:g id="NUMBER_2">%3$d</xliff:g> segundos."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remover"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir em volume alto por longos períodos pode danificar sua audição."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usar atalho de Acessibilidade?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quando o atalho está ativado, pressione os dois botões de volume por três segundos para iniciar um recurso de acessibilidade.\n\n Recurso de acessibilidade atual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n É possível alterar o recurso em Configurações > Acessibilidade."</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 4f45517c0bb9..81f5ced0a044 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Desenhou o padrão de desbloqueio incorretamente <xliff:g id="NUMBER_0">%1$d</xliff:g> vezes. Depois de mais <xliff:g id="NUMBER_1">%2$d</xliff:g> tentativas sem sucesso, ser-lhe-á pedido para desbloquear o telemóvel através de uma conta de email.\n\n Tente novamente dentro de <xliff:g id="NUMBER_2">%3$d</xliff:g> segundos."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" - "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remover"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir com um volume elevado durante longos períodos poderá ser prejudicial para a sua audição."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Pretende utilizar o atalho de acessibilidade?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quando o atalho está ativado, premir ambos os botões de volume durante 3 segundos inicia uma funcionalidade de acessibilidade.\n\n Funcionalidade de acessibilidade atual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Pode alterar a funcionalidade em Definições > Acessibilidade."</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 72a3ce904a8c..a6fd0fe230ec 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Você desenhou sua sequência de desbloqueio incorretamente <xliff:g id="NUMBER_0">%1$d</xliff:g> vezes. Se fizer mais <xliff:g id="NUMBER_1">%2$d</xliff:g> tentativas incorretas, será solicitado que você use o login do Google para desbloquear.\n\n Tente novamente em <xliff:g id="NUMBER_2">%3$d</xliff:g> segundos."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remover"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Aumentar o volume acima do nível recomendado?\n\nOuvir em volume alto por longos períodos pode danificar sua audição."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Usar atalho de Acessibilidade?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Quando o atalho está ativado, pressione os dois botões de volume por três segundos para iniciar um recurso de acessibilidade.\n\n Recurso de acessibilidade atual:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n É possível alterar o recurso em Configurações > Acessibilidade."</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index d7cdade6c674..cb8d3fee32ca 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1635,6 +1635,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Ați desenat incorect modelul pentru deblocare de <xliff:g id="NUMBER_0">%1$d</xliff:g> ori. După încă <xliff:g id="NUMBER_1">%2$d</xliff:g> încercări nereușite, vi se va solicita să deblocați telefonul cu ajutorul unui cont de e-mail.\n\n Încercați din nou peste <xliff:g id="NUMBER_2">%3$d</xliff:g> secunde."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Eliminați"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ridicați volumul mai sus de nivelul recomandat?\n\nAscultarea la volum ridicat pe perioade lungi de timp vă poate afecta auzul."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Utilizați comanda rapidă pentru accesibilitate?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Când comanda rapidă este activată, dacă apăsați ambele butoane de volum timp de 3 secunde, veți lansa o funcție de accesibilitate.\n\n Funcția actuală de accesibilitate:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Puteți schimba funcția în Setări > Accesibilitate."</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 91b7ca6ab62c..4821c7f9d175 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1657,6 +1657,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Вы <xliff:g id="NUMBER_0">%1$d</xliff:g> раз неверно указали графический ключ. После <xliff:g id="NUMBER_1">%2$d</xliff:g> неверных попыток для разблокировки телефона потребуется войти в аккаунт Google.\n\nПовтор через <xliff:g id="NUMBER_2">%3$d</xliff:g> сек."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Удалить"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Установить громкость выше рекомендуемого уровня?\n\nВоздействие громкого звука в течение долгого времени может привести к повреждению слуха."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Использовать быстрое включение?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Чтобы использовать функцию специальных возможностей, когда она включена, нажмите и удерживайте три секунды обе кнопки регулировки громкости.\n\nТекущая функция специальных возможностей:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\nВы можете изменить ее в разделе \"Настройки > Специальные возможности\"."</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 601f9b9ee6c6..df14036c1a07 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -1615,6 +1615,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"ඔබ වැරදියට <xliff:g id="NUMBER_0">%1$d</xliff:g> වතාවක් ඔබගේ අගුළු හැරීමේ රටාව ඇඳ ඇත. අසාර්ථක උත්සහ කිරීම් <xliff:g id="NUMBER_1">%2$d</xliff:g> න් පසුව, ඔබගේ ඊ-තැපැල් ලිපිනය භාවිතයෙන් ඔබගේ දුරකථනය අගුළු හැරීමට ඔබගෙන් අසයි.\n\n තත්පර <xliff:g id="NUMBER_2">%3$d</xliff:g> න් පසුව නැවත උත්සහ කරන්න."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ඉවත් කරන්න"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"නිර්දේශිතයි මට්ටමට වඩා ශබ්දය වැඩිද?\n\nදිගු කාලයක් සඳහා ඉහළ ශබ්දයක් ඇසීමෙන් ඇතැම් විට ඔබගේ ඇසීමට හානි විය හැක."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ප්රවේශ්යතා කෙටිමඟ භාවිතා කරන්නද?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"කෙටිමඟ සක්රිය විට, හඬ බොත්තම් දෙකම තත්පර 3ක් අල්ලාගෙන සිටීමෙන් ප්රවේශ්යත අංගයක් ඇරඹේ.\n\n වත්මන් ප්රවේශ්යතා අංගය:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n සැකසීම් > ප්රවේශ්යතාව තුළ ඔබට අංගය වෙනස් කළ හැක."</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index fb81855b4590..b0dc5f3cd342 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1657,6 +1657,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"<xliff:g id="NUMBER_0">%1$d</xliff:g>-krát ste nesprávne nakreslili svoj bezpečnostný vzor. Po <xliff:g id="NUMBER_1">%2$d</xliff:g> ďalších neúspešných pokusoch sa zobrazí výzva na odomknutie telefónu pomocou e-mailového účtu.\n\n Skúste to znova o <xliff:g id="NUMBER_2">%3$d</xliff:g> s."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Odstrániť"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Zvýšiť hlasitosť nad odporúčanú úroveň?\n\nDlhodobé počúvanie pri vysokej hlasitosti môže poškodiť váš sluch."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Použiť skratku dostupnosti?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Keď je skratka zapnutá, stlačením obidvoch tlačidiel hlasitosti na tri sekundy spustíte funkciu dostupnosti.\n\n Aktuálna funkcia dostupnosti:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funkciu môžete zmeniť v časti Nastavenia > Dostupnosť."</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 9405fb2a4e13..bdd5925d8a3e 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1657,6 +1657,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Vzorec za odklepanje ste <xliff:g id="NUMBER_0">%1$d</xliff:g>-krat napačno vnesli. Po nadaljnjih <xliff:g id="NUMBER_1">%2$d</xliff:g> neuspešnih poskusih boste pozvani, da odklenete telefon z Googlovimi podatki za prijavo.\n\nPoskusite znova čez <xliff:g id="NUMBER_2">%3$d</xliff:g> s."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Odstrani"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ali želite povečati glasnost nad priporočeno raven?\n\nDolgotrajno poslušanje pri veliki glasnosti lahko poškoduje sluh."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Želite uporabljati bližnjico funkcij za ljudi s posebnimi potrebami?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Ko je bližnjica vklopljena, pritisnite gumba za glasnost in ju pridržite tri sekunde, če želite zagnati funkcijo za ljudi s posebnimi potrebami.\n\n Trenutna funkcija za ljudi s posebnimi potrebami:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Funkcijo lahko spremenite v »Nastavitve > Funkcije za ljudi s posebnimi potrebami«."</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index fe167da393fa..fd8dda060448 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Ke vizatuar <xliff:g id="NUMBER_0">%1$d</xliff:g> herë pa sukses motivin tënd. Pas <xliff:g id="NUMBER_1">%2$d</xliff:g> tentativave të tjera të pasuksesshme, do të të duhet ta shkyçësh telefonin duke përdorur një llogari mail-i.\n\n Provo sërish për <xliff:g id="NUMBER_2">%3$d</xliff:g> sekonda."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" - "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Hiq"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Të ngrihet volumi mbi nivelin e rekomanduar?\n\nDëgjimi me volum të lartë për periudha të gjata mund të dëmtojë dëgjimin."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Të përdoret shkurtorja e qasshmërisë?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kur shkurtorja është e aktivizuar, shtypja e të dy butonave për 3 sekonda do të nisë një funksion qasshmërie.\n\n Funksioni aktual i qasshmërisë:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Mund ta ndryshosh funksionin te Cilësimet > Qasshmëria."</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 564612c454ce..d82faa66ed12 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1635,6 +1635,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Нацртали сте шаблон за откључавање нетачно <xliff:g id="NUMBER_0">%1$d</xliff:g> пута. После још <xliff:g id="NUMBER_1">%2$d</xliff:g> неуспешна(их) покушаја, од вас ће бити затражено да откључате телефон помоћу налога е-поште.\n\nПробајте поново за <xliff:g id="NUMBER_2">%3$d</xliff:g> секунде/и."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Уклони"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Желите да појачате звук изнад препорученог нивоа?\n\nСлушање гласне музике дуже време може да вам оштети слух."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Желите ли да користите пречицу за приступачност?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Када је пречица укључена, притисните оба дугмета за јачину звука да бисте покренули функцију приступачности.\n\n Актуелна функција приступачности:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Можете да промените функцију у одељку Подешавања > Приступачност."</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 4332352909f6..9c7e1e5ecc4b 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%1$d</xliff:g> gånger. Efter ytterligare <xliff:g id="NUMBER_1">%2$d</xliff:g> försök ombeds du låsa upp mobilen med hjälp av ett e-postkonto.\n\n Försök igen om <xliff:g id="NUMBER_2">%3$d</xliff:g> sekunder."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Ta bort"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Vill du höja volymen över den rekommenderade nivån?\n\nAtt lyssna med stark volym långa stunder åt gången kan skada hörseln."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Vill du använda Aktivera tillgänglighet snabbt?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"När kortkommandot har aktiverats startar du en tillgänglighetsfunktion genom att trycka ned båda volymknapparna i tre sekunder.\n\n Aktuell tillgänglighetsfunktion:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Du kan ändra funktionen i Inställningar > Tillgänglighet."</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index d1bff3f55906..f53efe068aec 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Umekosea kuchora mchoro wako wa kufungua mara <xliff:g id="NUMBER_0">%1$d</xliff:g>. Baada ya majaribio <xliff:g id="NUMBER_1">%2$d</xliff:g> yasiyofaulu, utaombwa kufungua simu yako kwa kutumia akaunti ya barua pepe.\n\n Jaribu tena baada ya sekunde <xliff:g id="NUMBER_2">%3$d</xliff:g>."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Ondoa"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ungependa kupandisha sauti zaidi ya kiwango kinachopendekezwa?\n\nKusikiliza kwa sauti ya juu kwa muda mrefu kunaweza kuharibu uwezo wako wa kusikia."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Ungependa kutumia njia ya mkato ya ufikivu?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Unapowasha kipengele cha njia ya mkato, hatua ya kubonyeza vitufe vyote viwili vya sauti kwa dakika 3 itafungua kipengele cha ufikivu.\n\n Kipengele cha ufikivu kilichopo kwa sasa:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Unaweza kubadilisha kipengele hiki katika Mipangilio > Zana za ufikivu."</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 5444f36081f3..c7c4ea3c6a5b 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"திறப்பதற்கான வடிவத்தை <xliff:g id="NUMBER_0">%1$d</xliff:g> முறை தவறாக வரைந்துள்ளீர்கள். மேலும் <xliff:g id="NUMBER_1">%2$d</xliff:g> தோல்வி முயற்சிகளுக்குப் பிறகு, மின்னஞ்சல் கணக்கைப் பயன்படுத்தி உங்கள் மொபைலைத் திறக்கக் கேட்கப்படுவீர்கள்.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> வினாடிகள் கழித்து முயற்சிக்கவும்."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"அகற்று"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"பரிந்துரைத்த அளவை விட ஒலியை அதிகரிக்கவா?\n\nநீண்ட நேரத்திற்கு அதிகளவில் ஒலி கேட்பது கேட்கும் திறனைப் பாதிக்கலாம்."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"அணுகல்தன்மை ஷார்ட்கட்டைப் பயன்படுத்தவா?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"ஷார்ட்கட் இயக்கத்தில் இருந்தால், இரண்டு ஒலியளவு பொத்தான்களையும் 3 வினாடிகள் அழுத்தி, அணுகல்தன்மை அம்சத்தை இயக்கலாம்.\n\n தற்போதைய அணுகல்தன்மை அம்சம்:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n அமைப்புகள் > அணுகல்தன்மை என்பதற்குச் சென்று, அம்சத்தை மாற்றலாம்."</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index f9f6479eece2..9c77ccd01b92 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"మీరు మీ అన్లాక్ నమూనాను <xliff:g id="NUMBER_0">%1$d</xliff:g> సార్లు తప్పుగా గీసారు. మరో <xliff:g id="NUMBER_1">%2$d</xliff:g> విఫల ప్రయత్నాల తర్వాత, ఇమెయిల్ ఖాతాను ఉపయోగించి మీ ఫోన్ను అన్లాక్ చేయాల్సిందిగా మిమ్మల్ని అడుగుతారు.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> సెకన్లలో మళ్లీ ప్రయత్నించండి."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"తీసివేయి"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"వాల్యూమ్ను సిఫార్సు చేయబడిన స్థాయి కంటే ఎక్కువగా పెంచాలా?\n\nసుదీర్ఘ వ్యవధుల పాటు అధిక వాల్యూమ్లో వినడం వలన మీ వినికిడి శక్తి దెబ్బ తినవచ్చు."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"యాక్సెస్ సామర్థ్యం షార్ట్కట్ను ఉపయోగించాలా?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"షార్ట్కట్ ఆన్లో ఉన్నప్పుడు, రెండు వాల్యూమ్ బటన్లను 3 సెకన్ల పాటు నొక్కితే యాక్సెస్ సామర్థ్య ఫీచర్ ప్రారంభం అవుతుంది.\n\n ప్రస్తుత యాక్సెస్ సామర్థ్య ఫీచర్:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n సెట్టింగ్లు > యాక్సెస్ సామర్థ్యంలో మీరు ఫీచర్ను మార్చవచ్చు."</string> @@ -1987,8 +1989,7 @@ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> స్ప్రెడ్షీట్"</string> <string name="mime_type_presentation" msgid="1145384236788242075">"ప్రదర్శన"</string> <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> ప్రదర్శన"</string> - <!-- no translation found for bluetooth_airplane_mode_toast (2066399056595768554) --> - <skip /> + <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"విమానం మోడ్లో బ్లూటూత్ ఆన్లో ఉంటుంది"</string> <string name="car_loading_profile" msgid="8219978381196748070">"లోడవుతోంది"</string> <plurals name="file_count" formatted="false" msgid="7063513834724389247"> <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> ఫైల్లు</item> @@ -2009,8 +2010,7 @@ <!-- no translation found for accessibility_system_action_accessibility_menu_label (8436484650391125184) --> <skip /> <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> క్యాప్షన్ బార్."</string> - <!-- no translation found for as_app_forced_to_restricted_bucket (8233871289353898964) --> - <skip /> + <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> పరిమితం చేయబడిన బకెట్లో ఉంచబడింది"</string> <!-- no translation found for resolver_personal_tab (2051260504014442073) --> <skip /> <!-- no translation found for resolver_work_tab (2690019516263167035) --> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 7796b03b1ae0..efff25bf74c8 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"คุณวาดรูปแบบการปลดล็อกไม่ถูกต้อง <xliff:g id="NUMBER_0">%1$d</xliff:g> ครั้งแล้ว หากทำไม่สำเร็จอีก <xliff:g id="NUMBER_1">%2$d</xliff:g> ครั้ง ระบบจะขอให้คุณปลดล็อกโทรศัพท์โดยใช้ับัญชีอีเมล\n\n โปรดลองอีกครั้งในอีก <xliff:g id="NUMBER_2">%3$d</xliff:g> วินาที"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ลบ"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"นี่เป็นการเพิ่มระดับเสียงเกินระดับที่แนะนำ\n\nการฟังเสียงดังเป็นเวลานานอาจทำให้การได้ยินของคุณบกพร่องได้"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ใช้ทางลัดการช่วยเหลือพิเศษไหม"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"เมื่อทางลัดเปิดอยู่ การกดปุ่มปรับระดับเสียงทั้ง 2 ปุ่มเป็นเวลา 3 วินาทีจะเริ่มฟีเจอร์การช่วยเหลือพิเศษ\n\n ฟีเจอร์การช่วยเหลือพิเศษปัจจุบัน:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n คุณสามารถเปลี่ยนฟีเจอร์ในการตั้งค่า > การช่วยเหลือพิเศษ"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index d15bcd7f48d7..7cfa7b02ebaa 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Naguhit mo nang hindi tama ang iyong pattern sa pag-unlock nang <xliff:g id="NUMBER_0">%1$d</xliff:g> (na) beses. Pagkatapos ng <xliff:g id="NUMBER_1">%2$d</xliff:g> pang hindi matagumpay na pagtatangka, hihilingin sa iyong i-unlock ang telepono mo gamit ang isang email account.\n\n Subukang muli sa loob ng <xliff:g id="NUMBER_2">%3$d</xliff:g> (na) segundo."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Alisin"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Lakasan ang volume nang lagpas sa inirerekomendang antas?\n\nMaaaring mapinsala ng pakikinig sa malakas na volume sa loob ng mahahabang panahon ang iyong pandinig."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Gagamitin ang Shortcut sa Pagiging Accessible?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kapag naka-on ang shortcut, magsisimula ang isang feature ng pagiging naa-access kapag pinindot ang parehong button ng volume sa loob ng 3 segundo.\n\n Kasalukuyang feature ng pagiging naa-access:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Maaari mong baguhin ang feature sa Mga Setting > Pagiging Accessible."</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index e5559c507c30..b47eb4bb025c 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Kilit açma deseninizi <xliff:g id="NUMBER_0">%1$d</xliff:g> kez yanlış çizdiniz. <xliff:g id="NUMBER_1">%2$d</xliff:g> başarısız denemeden sonra telefonunuzu bir e-posta hesabı kullanarak açmanız istenir.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> saniye içinde tekrar deneyin."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Kaldır"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Ses seviyesi önerilen düzeyin üzerine yükseltilsin mi?\n\nUzun süre yüksek ses seviyesinde dinlemek işitme duyunuza zarar verebilir."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Erişilebilirlik Kısayolu Kullanılsın mı?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Kısayol açık olduğunda, ses düğmelerinin ikisini birden 3 saniyeliğine basılı tutmanız bir erişilebilirlik özelliğini başlatır.\n\n Geçerli erişilebilirlik özelliği:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Özelliği, Ayarlar > Erişilebilirlik seçeneğinden değiştirebilirsiniz."</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 7c06892e070c..22aa199d9964 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1657,6 +1657,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Ключ розблокування неправильно намальовано стільки разів: <xliff:g id="NUMBER_0">%1$d</xliff:g>. У вас є ще стільки спроб: <xliff:g id="NUMBER_1">%2$d</xliff:g>. У разі невдачі з’явиться запит розблокувати телефон за допомогою облікового запису електронної пошти.\n\n Повторіть спробу через <xliff:g id="NUMBER_2">%3$d</xliff:g> сек."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" – "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Вилучити"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Збільшити гучність понад рекомендований рівень?\n\nЯкщо слухати надто гучну музику тривалий час, можна пошкодити слух."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Використовувати швидке ввімкнення?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Коли ярлик увімкнено, після натискання обох клавіш гучності й утримування їх протягом 3 секунд увімкнеться функція спеціальних можливостей.\n\n Поточна функція спеціальних можливостей:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Цю функцію можна змінити в меню \"Налаштування\" > \"Спеціальні можливості\"."</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 1b007200def6..a88d6c504964 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"آپ نے اپنا غیر مقفل کرنے کا پیٹرن <xliff:g id="NUMBER_0">%1$d</xliff:g> بار غلط طریقے سے ڈرا کیا ہے۔ <xliff:g id="NUMBER_1">%2$d</xliff:g> مزید ناکام کوششوں کے بعد، آپ سے ایک ای میل اکاؤنٹ استعمال کرکے اپنا فون غیر مقفل کرنے کو کہا جائے گا۔\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> سیکنڈ میں دوبارہ کوشش کریں۔"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"ہٹائیں"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"والیوم کو تجویز کردہ سطح سے زیادہ کریں؟\n\nزیادہ وقت تک اونچی آواز میں سننے سے آپ کی سماعت کو نقصان پہنچ سکتا ہے۔"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"ایکسیسبیلٹی شارٹ کٹ استعمال کریں؟"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"شارٹ کٹ آن ہونے پر، 3 سیکنڈ تک دونوں والیوم بٹنز کو دبانے سے ایک ایکسیسبیلٹی خصوصیت شروع ہو جائے گی۔\n\n موجودہ ایکسیسبیلٹی خصوصیت:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n آپ خصوصیت کو ترتیبات > ایکسیسبیلٹی میں جا کر تبدیل کر سکتے ہیں۔"</string> @@ -1987,8 +1989,7 @@ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> اسپریڈشیٹ"</string> <string name="mime_type_presentation" msgid="1145384236788242075">"پیشکش"</string> <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> پیشکش"</string> - <!-- no translation found for bluetooth_airplane_mode_toast (2066399056595768554) --> - <skip /> + <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"ہوائی جہاز وضع کے دوران بلوٹوتھ آن رہے گا"</string> <string name="car_loading_profile" msgid="8219978381196748070">"لوڈ ہو رہا ہے"</string> <plurals name="file_count" formatted="false" msgid="7063513834724389247"> <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> فائلز</item> @@ -2009,8 +2010,7 @@ <!-- no translation found for accessibility_system_action_accessibility_menu_label (8436484650391125184) --> <skip /> <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g> کی کیپشن بار۔"</string> - <!-- no translation found for as_app_forced_to_restricted_bucket (8233871289353898964) --> - <skip /> + <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> کو پابند کردہ بکٹ میں رکھ دیا گیا ہے"</string> <!-- no translation found for resolver_personal_tab (2051260504014442073) --> <skip /> <!-- no translation found for resolver_work_tab (2690019516263167035) --> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 1193d278774d..a08b1788c5d6 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Siz grafik kalitni <xliff:g id="NUMBER_0">%1$d</xliff:g> marta noto‘g‘ri chizdingiz. <xliff:g id="NUMBER_1">%2$d</xliff:g> marta muvaffaqiyatsiz urinishdan so‘ng, sizdan e-pochtangizdan foydalanib, telefon qulfini ochishingiz so‘raladi.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> soniyadan so‘ng yana urinib ko‘ring."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Olib tashlash"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Tovush balandligi tavsiya etilgan darajadan ham yuqori qilinsinmi?\n\nUzoq vaqt davomida baland ovozda tinglash eshitish qobiliyatingizga salbiy ta’sir ko‘rsatishi mumkin."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Tezkor ishga tushirishdan foydalanilsinmi?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Maxsus imkoniyatlar funksiyasidan foydalanish uchun u yoniqligida ikkala ovoz balandligini boshqarish tugmasini 3 soniya bosib turing.\n\n Joriy maxsus imkoniyatlar funksiyasi:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Bu funksiyani Sozlamalar > Maxsus imkoniyatlar orqali o‘zgartirish mumkin."</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 7cfd87051a2b..db55613dce7e 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Bạn đã <xliff:g id="NUMBER_0">%1$d</xliff:g> lần vẽ không chính xác hình mở khóa của mình. Sau <xliff:g id="NUMBER_1">%2$d</xliff:g> lần thử không thành công nữa, bạn sẽ được yêu cầu mở khóa điện thoại bằng tài khoản email.\n\n Vui lòng thử lại sau <xliff:g id="NUMBER_2">%3$d</xliff:g> giây."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Xóa"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Bạn tăng âm lượng lên quá mức khuyên dùng?\n\nViệc nghe ở mức âm lượng cao trong thời gian dài có thể gây tổn thương thính giác của bạn."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Sử dụng phím tắt Hỗ trợ tiếp cận?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Khi phím tắt được bật, nhấn cả hai nút âm lượng trong 3 giây sẽ bắt đầu một tính năng trợ năng.\n\n Tính năng trợ năng hiện tại:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Bạn có thể thay đổi tính năng trong Cài đặt > Trợ năng."</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 5d7ad5ad0361..08d232683c57 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"您已连续 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次画错解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次后仍不成功,系统就会要求您使用自己的电子邮件帐号解锁手机。\n\n请在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒后重试。"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"删除"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要将音量调高到建议的音量以上吗?\n\n长时间保持高音量可能会损伤听力。"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用无障碍快捷方式吗?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"开启快捷方式后,同时按下两个音量按钮 3 秒钟即可启动所设定的无障碍功能。\n\n当前设定的无障碍功能:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n如需更改设定的功能,请依次转到“设置”>“无障碍”。"</string> @@ -1987,8 +1989,7 @@ <string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"<xliff:g id="EXTENSION">%1$s</xliff:g> 电子表格"</string> <string name="mime_type_presentation" msgid="1145384236788242075">"演示文稿"</string> <string name="mime_type_presentation_ext" msgid="8761049335564371468">"<xliff:g id="EXTENSION">%1$s</xliff:g> 演示文稿"</string> - <!-- no translation found for bluetooth_airplane_mode_toast (2066399056595768554) --> - <skip /> + <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"在飞行模式下,蓝牙将保持开启状态"</string> <string name="car_loading_profile" msgid="8219978381196748070">"正在加载"</string> <plurals name="file_count" formatted="false" msgid="7063513834724389247"> <item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> 个文件</item> @@ -2009,8 +2010,7 @@ <!-- no translation found for accessibility_system_action_accessibility_menu_label (8436484650391125184) --> <skip /> <string name="accessibility_freeform_caption" msgid="8377519323496290122">"<xliff:g id="APP_NAME">%1$s</xliff:g>的标题栏。"</string> - <!-- no translation found for as_app_forced_to_restricted_bucket (8233871289353898964) --> - <skip /> + <string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> 已被放入受限存储分区"</string> <!-- no translation found for resolver_personal_tab (2051260504014442073) --> <skip /> <!-- no translation found for resolver_work_tab (2690019516263167035) --> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 361eb4c66029..ea106c6c2a78 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"您已畫錯解鎖圖案 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統會要求您透過電郵帳戶解開上鎖的手機。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"移除"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要調高音量 (比建議的音量更大聲) 嗎?\n\n長時間聆聽高分貝音量可能會導致您的聽力受損。"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用無障礙功能快速鍵嗎?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"快速鍵開啟後,同時按住音量按鈕 3 秒,無障礙功能便會啟用。\n\n目前的無障礙功能:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n如要變更功能,請前往「設定」>「無障礙功能」。"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 2bcae59eb4cf..70651816bfbd 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"你的解鎖圖案已畫錯 <xliff:g id="NUMBER_0">%1$d</xliff:g> 次,如果再嘗試 <xliff:g id="NUMBER_1">%2$d</xliff:g> 次仍未成功,系統就會要求你透過電子郵件帳戶解除手機的鎖定狀態。\n\n請在 <xliff:g id="NUMBER_2">%3$d</xliff:g> 秒後再試一次。"</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"移除"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"要調高音量,比建議的音量更大聲嗎?\n\n長時間聆聽高分貝音量可能會使你的聽力受損。"</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"要使用無障礙捷徑嗎?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"啟用捷徑功能後,只要同時按下兩個音量鍵 3 秒,就能啟動無障礙功能。\n\n 目前設定的無障礙功能為:\n<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n 如要變更設定的功能,請依序輕觸 [設定] > [無障礙設定]。"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index e96696ea0f4a..1cb67e162da7 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1613,6 +1613,8 @@ <string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"Ukulayisha ungenisa iphathini yakho yokuvula ngendlela engalungile izikhathi ezi-<xliff:g id="NUMBER_0">%1$d</xliff:g> Emva kweminye imizamo engu-<xliff:g id="NUMBER_1">%2$d</xliff:g>, uzocelwa ukuvula ifoni yakho usebenzisa ukungena ngemvume ku-Google\n\n Zame futhi emumva kwengu- <xliff:g id="NUMBER_2">%3$d</xliff:g> amasekhondi."</string> <string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string> <string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Susa"</string> + <!-- no translation found for allow_while_in_use_permission_in_fgs (4101339676785053656) --> + <skip /> <string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Khuphukisa ivolumu ngaphezu kweleveli enconyiwe?\n\nUkulalela ngevolumu ephezulu izikhathi ezide kungahle kulimaze ukuzwa kwakho."</string> <string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Sebenzisa isinqamuleli sokufinyelela?"</string> <string name="accessibility_shortcut_toogle_warning" msgid="8306239551412868396">"Uma kuvulwe isinqamuleli, ukucindezela zombili izinkinobho zevolumu amasekhondi angu-3 kuzoqala isici sokufinyelela.\n\n Isici samanje sokufinyelela:\n <xliff:g id="SERVICE_NAME">%1$s</xliff:g>\n\n Ungashintsha isici kuzilungiselelo > Ukufinyelela."</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index dadb92415839..e98f8ba3cda7 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2281,6 +2281,9 @@ movement threshold under which hover is considered "stationary". --> <dimen name="config_viewConfigurationHoverSlop">4dp</dimen> + <!-- Multiplier for gesture thresholds when a MotionEvent classification is ambiguous. --> + <item name="config_ambiguousGestureMultiplier" format="float" type="dimen">2.0</item> + <!-- Minimum velocity to initiate a fling, as measured in dips per second. --> <dimen name="config_viewMinFlingVelocity">50dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d2384859531c..e4717f817688 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -482,6 +482,7 @@ <java-symbol type="dimen" name="config_prefDialogWidth" /> <java-symbol type="dimen" name="config_viewConfigurationTouchSlop" /> <java-symbol type="dimen" name="config_viewConfigurationHoverSlop" /> + <java-symbol type="dimen" name="config_ambiguousGestureMultiplier" /> <java-symbol type="dimen" name="config_viewMinFlingVelocity" /> <java-symbol type="dimen" name="config_viewMaxFlingVelocity" /> <java-symbol type="dimen" name="config_scrollbarSize" /> diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp index 53db8322f7b8..fec4628106a5 100644 --- a/core/tests/ResourceLoaderTests/Android.bp +++ b/core/tests/ResourceLoaderTests/Android.bp @@ -32,15 +32,16 @@ android_test { "truth-prebuilt", ], resource_zips: [ ":FrameworksResourceLoaderTestsAssets" ], + platform_apis: true, test_suites: ["device-tests"], - sdk_version: "test_current", aaptflags: [ "--no-compress", ], data: [ - ":FrameworksResourceLoaderTestsOverlay", ":FrameworksResourceLoaderTestsSplitOne", ":FrameworksResourceLoaderTestsSplitTwo", + ":FrameworksResourceLoaderTestsSplitThree", + ":FrameworksResourceLoaderTestsSplitFour", ], java_resources: [ "NonAsset.txt" ] } diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml index 702151d01110..d732132bef02 100644 --- a/core/tests/ResourceLoaderTests/AndroidTest.xml +++ b/core/tests/ResourceLoaderTests/AndroidTest.xml @@ -22,13 +22,7 @@ <option name="cleanup-apks" value="true" /> <!-- The following value cannot be multi-line as whitespace is parsed by the installer --> <option name="split-apk-file-names" - value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk" /> - <option name="test-file-name" value="FrameworksResourceLoaderTestsOverlay.apk" /> - </target_preparer> - - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" - value="cmd overlay disable android.content.res.loader.test.overlay" /> + value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk,FrameworksResourceLoaderTestsSplitThree.apk,FrameworksResourceLoaderTestsSplitFour.apk" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png Binary files differindex efd71ee039e2..8102d1539d53 100644 --- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png +++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml index d59059b453d6..05499ed35e50 100644 --- a/core/tests/ResourceLoaderTests/res/layout/layout.xml +++ b/core/tests/ResourceLoaderTests/res/layout/layout.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> -<FrameLayout +<MysteryLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh index 885f681f4261..8e05aefccd39 100755 --- a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh +++ b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh @@ -68,9 +68,13 @@ mkdir -p "$genDir"/temp/res/layout compileAndLink stringOne BOTH AndroidManifestFramework.xml res/values/string_one.xml compileAndLink stringTwo BOTH AndroidManifestFramework.xml res/values/string_two.xml +compileAndLink stringThree BOTH AndroidManifestFramework.xml res/values/string_three.xml +compileAndLink stringFour BOTH AndroidManifestFramework.xml res/values/string_four.xml compileAndLink dimenOne BOTH AndroidManifestFramework.xml res/values/dimen_one.xml compileAndLink dimenTwo BOTH AndroidManifestFramework.xml res/values/dimen_two.xml +compileAndLink dimenThree BOTH AndroidManifestFramework.xml res/values/dimen_three.xml +compileAndLink dimenFour BOTH AndroidManifestFramework.xml res/values/dimen_four.xml compileAndLink drawableMdpiWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png compileAndLink drawableMdpiWithFile APK AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png @@ -86,6 +90,14 @@ cp -f "$inDir"/res/layout/layout_two.xml "$genDir"/temp/res/layout/layout.xml compileAndLink layoutTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml cp -f "$genDir"/out/layoutTwo/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutTwo.xml +cp -f "$inDir"/res/layout/layout_three.xml "$genDir"/temp/res/layout/layout.xml +compileAndLink layoutThree ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml +cp -f "$genDir"/out/layoutThree/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutThree.xml + +cp -f "$inDir"/res/layout/layout_four.xml "$genDir"/temp/res/layout/layout.xml +compileAndLink layoutFour ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml +cp -f "$genDir"/out/layoutFour/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutFour.xml + drawableNoDpi="/res/drawable-nodpi" inDirDrawableNoDpi="$inDir$drawableNoDpi" @@ -97,6 +109,18 @@ cp -f "$inDirDrawableNoDpi"/nonAssetDrawableTwo.xml "$genDir"/temp/res/drawable- compileAndLink nonAssetDrawableTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml cp -f "$genDir"/out/nonAssetDrawableTwo/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableTwo.xml +cp -f "$inDirDrawableNoDpi"/nonAssetDrawableThree.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml +compileAndLink nonAssetDrawableThree ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml +cp -f "$genDir"/out/nonAssetDrawableThree/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableThree.xml + +cp -f "$inDirDrawableNoDpi"/nonAssetDrawableFour.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml +compileAndLink nonAssetDrawableFour ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml +cp -f "$genDir"/out/nonAssetDrawableFour/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableFour.xml + +cp -f "$inDirDrawableNoDpi"/nonAssetBitmapRed.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png +compileAndLink nonAssetBitmapRed BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml +cp -f "$genDir"/out/nonAssetBitmapRed/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapRed.png + cp -f "$inDirDrawableNoDpi"/nonAssetBitmapGreen.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png compileAndLink nonAssetBitmapGreen BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml cp -f "$genDir"/out/nonAssetBitmapGreen/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapGreen.png @@ -105,4 +129,8 @@ cp -f "$inDirDrawableNoDpi"/nonAssetBitmapBlue.png "$genDir"/temp/res/drawable-n compileAndLink nonAssetBitmapBlue ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml cp -f "$genDir"/out/nonAssetBitmapBlue/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapBlue.png +cp -f "$inDirDrawableNoDpi"/nonAssetBitmapWhite.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png +compileAndLink nonAssetBitmapWhite ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml +cp -f "$genDir"/out/nonAssetBitmapWhite/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapWhite.png + $soong_zip -o "$genDir"/out.zip -C "$genDir"/output/ -D "$genDir"/output/ diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png Binary files differnew file mode 100644 index 000000000000..4eb8ca3537ea --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png Binary files differnew file mode 100644 index 000000000000..e9a4cfcef316 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml new file mode 100644 index 000000000000..0623245c6152 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.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. + --> + +<color + xmlns:android="http://schemas.android.com/apk/res/android" + android:color="#000004" + /> diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml index f1a93d2d2f21..57a8cf1b86de 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml @@ -17,5 +17,5 @@ <color xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#A3C3E3" + android:color="#000001" /> diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml new file mode 100644 index 000000000000..41095d4a158b --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.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. + --> + +<color + xmlns:android="http://schemas.android.com/apk/res/android" + android:color="#000003" + /> diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml index 7c455a57fb0b..333fe346998c 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml @@ -17,5 +17,5 @@ <color xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#3A3C3E" + android:color="#000002" /> diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml new file mode 100644 index 000000000000..ab9e26529fe7 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml @@ -0,0 +1,23 @@ +<?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. + --> + +<TableLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml new file mode 100644 index 000000000000..d58d3db12ad4 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml @@ -0,0 +1,23 @@ +<?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" + /> + diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml index 348bb353611a..5b30eba5953b 100644 --- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml +++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2019 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. @@ -16,7 +16,6 @@ --> <resources> - - <string name="loader_path_change_test">Overlaid</string> - + <public type="dimen" name="app_icon_size" id="0x01050000" /> + <dimen name="app_icon_size">400dp</dimen> </resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml index 69ecf2316284..b17ec1c66717 100644 --- a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml +++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml @@ -17,5 +17,5 @@ <resources> <public type="dimen" name="app_icon_size" id="0x01050000" /> - <dimen name="app_icon_size">564716dp</dimen> + <dimen name="app_icon_size">100dp</dimen> </resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml new file mode 100644 index 000000000000..07a35cedd886 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.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> + <public type="dimen" name="app_icon_size" id="0x01050000" /> + <dimen name="app_icon_size">300dp</dimen> +</resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml index 4d55deffbd2a..570b40aa7a7a 100644 --- a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml +++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml @@ -17,5 +17,5 @@ <resources> <public type="dimen" name="app_icon_size" id="0x01050000" /> - <dimen name="app_icon_size">565717dp</dimen> + <dimen name="app_icon_size">200dp</dimen> </resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_four.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_four.xml new file mode 100644 index 000000000000..8789bcdb066c --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/values/string_four.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> + <public type="string" name="cancel" id="0x01040000" /> + <string name="cancel">SomeRidiculouslyUnlikelyStringFour</string> +</resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_three.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_three.xml new file mode 100644 index 000000000000..82cd6ec7270f --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/values/string_three.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> + <public type="string" name="cancel" id="0x01040000" /> + <string name="cancel">SomeRidiculouslyUnlikelyStringThree</string> +</resources> diff --git a/core/tests/ResourceLoaderTests/overlay/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp index 63e7e61d797a..eb4d8e1ac7f2 100644 --- a/core/tests/ResourceLoaderTests/overlay/Android.bp +++ b/core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp @@ -1,4 +1,5 @@ -// Copyright (C) 2018 The Android Open Source Project +// +// Copyright (C) 2019 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,10 +12,8 @@ // 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: "FrameworksResourceLoaderTestsOverlay", - sdk_version: "current", - - aaptflags: ["--no-resource-removal"], +android_test_helper_app { + name: "FrameworksResourceLoaderTestsSplitFour" } diff --git a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitFour/AndroidManifest.xml index 942f7da9aa27..24a0a2a64afb 100644 --- a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml +++ b/core/tests/ResourceLoaderTests/splits/SplitFour/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2019 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. @@ -17,11 +17,11 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.res.loader.test.overlay" + package="android.content.res.loader.test" + split="split_four" > + <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" /> <application android:hasCode="false" /> - <overlay android:targetPackage="android.content.res.loader.test" /> - </manifest> diff --git a/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml b/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml new file mode 100644 index 000000000000..4759db978dcb --- /dev/null +++ b/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.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> + <public type="string" name="split_overlaid" id="0x7f040001" /> + <string name="split_overlaid">Split FOUR Overlaid</string> +</resources> diff --git a/core/tests/ResourceLoaderTests/SplitOne/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp index 897897fbf254..897897fbf254 100644 --- a/core/tests/ResourceLoaderTests/SplitOne/Android.bp +++ b/core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp diff --git a/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitOne/AndroidManifest.xml index b14bd8600f31..b14bd8600f31 100644 --- a/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml +++ b/core/tests/ResourceLoaderTests/splits/SplitOne/AndroidManifest.xml diff --git a/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml b/core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml index 3c215ebc287c..3c215ebc287c 100644 --- a/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml +++ b/core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml diff --git a/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp new file mode 100644 index 000000000000..bf98a740cd88 --- /dev/null +++ b/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp @@ -0,0 +1,19 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +android_test_helper_app { + name: "FrameworksResourceLoaderTestsSplitThree" +} diff --git a/core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml new file mode 100644 index 000000000000..ae1579b178f3 --- /dev/null +++ b/core/tests/ResourceLoaderTests/splits/SplitThree/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="android.content.res.loader.test" + split="split_three" + > + + <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" /> + <application android:hasCode="false" /> + +</manifest> diff --git a/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml b/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml new file mode 100644 index 000000000000..97682aa1b5cf --- /dev/null +++ b/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.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> + <public type="string" name="split_overlaid" id="0x7f040001" /> + <string name="split_overlaid">Split THREE Overlaid</string> +</resources> diff --git a/core/tests/ResourceLoaderTests/splits/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp index 4582808934df..4582808934df 100644 --- a/core/tests/ResourceLoaderTests/splits/Android.bp +++ b/core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp diff --git a/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitTwo/AndroidManifest.xml index aad8c27a1a3b..aad8c27a1a3b 100644 --- a/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml +++ b/core/tests/ResourceLoaderTests/splits/SplitTwo/AndroidManifest.xml diff --git a/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml b/core/tests/ResourceLoaderTests/splits/SplitTwo/res/values/string_split.xml index a367063dd43e..a367063dd43e 100644 --- a/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml +++ b/core/tests/ResourceLoaderTests/splits/SplitTwo/res/values/string_split.xml diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt index b1bdc967e68f..9e94bdc8a081 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt @@ -16,8 +16,9 @@ package android.content.res.loader.test -import android.content.res.loader.DirectoryResourceLoader -import android.content.res.loader.ResourceLoader +import android.content.res.loader.AssetsProvider +import android.content.res.loader.DirectoryAssetsProvider +import android.content.res.loader.ResourcesLoader import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable @@ -29,18 +30,21 @@ import org.junit.Test import org.junit.rules.TestName import java.io.File -class DirectoryResourceLoaderTest : ResourceLoaderTestBase() { +class DirectoryAssetsProviderTest : ResourceLoaderTestBase() { @get:Rule val testName = TestName() private lateinit var testDir: File - private lateinit var loader: ResourceLoader + private lateinit var assetsProvider: AssetsProvider + private lateinit var loader: ResourcesLoader @Before fun setUpTestDir() { - testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}") - loader = DirectoryResourceLoader(testDir) + testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}") + assetsProvider = DirectoryAssetsProvider(testDir) + loader = ResourcesLoader() + resources.addLoader(loader) } @After @@ -51,29 +55,29 @@ class DirectoryResourceLoaderTest : ResourceLoaderTestBase() { @Test fun loadDrawableXml() { "nonAssetDrawableOne" writeTo "res/drawable-nodpi-v4/non_asset_drawable.xml" - val provider = openArsc("nonAssetDrawableOne") + val provider = openArsc("nonAssetDrawableOne", assetsProvider) fun getValue() = (resources.getDrawable(R.drawable.non_asset_drawable) as ColorDrawable) .color assertThat(getValue()).isEqualTo(Color.parseColor("#B2D2F2")) - addLoader(loader to provider) + loader.addProvider(provider) - assertThat(getValue()).isEqualTo(Color.parseColor("#A3C3E3")) + assertThat(getValue()).isEqualTo(Color.parseColor("#000001")) } @Test fun loadDrawableBitmap() { "nonAssetBitmapGreen" writeTo "res/drawable-nodpi-v4/non_asset_bitmap.png" - val provider = openArsc("nonAssetBitmapGreen") + val provider = openArsc("nonAssetBitmapGreen", assetsProvider) fun getValue() = (resources.getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable) .bitmap.getColor(0, 0).toArgb() - assertThat(getValue()).isEqualTo(Color.RED) + assertThat(getValue()).isEqualTo(Color.MAGENTA) - addLoader(loader to provider) + loader.addProvider(provider) assertThat(getValue()).isEqualTo(Color.GREEN) } @@ -81,13 +85,13 @@ class DirectoryResourceLoaderTest : ResourceLoaderTestBase() { @Test fun loadXml() { "layoutOne" writeTo "res/layout/layout.xml" - val provider = openArsc("layoutOne") + val provider = openArsc("layoutOne", assetsProvider) fun getValue() = resources.getLayout(R.layout.layout).advanceToRoot().name - assertThat(getValue()).isEqualTo("FrameLayout") + assertThat(getValue()).isEqualTo("MysteryLayout") - addLoader(loader to provider) + loader.addProvider(provider) assertThat(getValue()).isEqualTo("RelativeLayout") } diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt deleted file mode 100644 index a6a83789c082..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt +++ /dev/null @@ -1,169 +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 android.content.res.loader.test - -import android.content.res.AssetManager -import android.content.res.loader.DirectoryResourceLoader -import android.content.res.loader.ResourceLoader -import android.content.res.loader.ResourcesProvider -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TestName -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.anyString -import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.eq -import org.mockito.Mockito.inOrder -import org.mockito.Mockito.mock -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException -import java.nio.file.Paths - -@RunWith(Parameterized::class) -class ResourceLoaderAssetTest : ResourceLoaderTestBase() { - - companion object { - private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt" - private const val TEST_TEXT = "some text" - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun parameters(): Array<Array<out Any?>> { - val fromInputStream: ResourceLoader.(String) -> Any? = { - loadAsset(eq(it), anyInt()) - } - - val fromFileDescriptor: ResourceLoader.(String) -> Any? = { - loadAssetFd(eq(it)) - } - - val openAsset: AssetManager.() -> String? = { - open(BASE_TEST_PATH).reader().readText() - } - - val openNonAsset: AssetManager.() -> String? = { - openNonAssetFd(BASE_TEST_PATH).readText() - } - - return arrayOf( - arrayOf("assets", fromInputStream, openAsset), - arrayOf("", fromFileDescriptor, openNonAsset) - ) - } - } - - @get:Rule - val testName = TestName() - - @JvmField - @field:Parameterized.Parameter(0) - var prefix: String? = null - - @field:Parameterized.Parameter(1) - lateinit var loadAssetFunction: ResourceLoader.(String) -> Any? - - @field:Parameterized.Parameter(2) - lateinit var openAssetFunction: AssetManager.() -> String? - - private val testPath: String - get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString() - - private fun ResourceLoader.loadAsset() = loadAssetFunction(testPath) - - private fun AssetManager.openAsset() = openAssetFunction() - - private lateinit var testDir: File - - @Before - fun setUpTestDir() { - testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}") - testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT) - } - - @Test - fun multipleLoadersSearchesBackwards() { - // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it - val loader = DirectoryResourceLoader(testDir) - val loaderWrapper = mock(ResourceLoader::class.java).apply { - doAnswer { loader.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) } - .`when`(this).loadAsset(anyString(), anyInt()) - doAnswer { loader.loadAssetFd(it.arguments[0] as String) } - .`when`(this).loadAssetFd(anyString()) - } - - val one = loaderWrapper to ResourcesProvider.empty() - val two = mockLoader { - doReturn(null).`when`(it).loadAsset() - } - - addLoader(one, two) - - assertOpenedAsset() - inOrder(two.first, one.first).apply { - verify(two.first).loadAsset() - verify(one.first).loadAsset() - } - } - - @Test(expected = FileNotFoundException::class) - fun failToFindThrowsFileNotFound() { - val one = mockLoader { - doReturn(null).`when`(it).loadAsset() - } - val two = mockLoader { - doReturn(null).`when`(it).loadAsset() - } - - addLoader(one, two) - - assertOpenedAsset() - } - - @Test - fun throwingIOExceptionIsSkipped() { - val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty() - val two = mockLoader { - doAnswer { throw IOException() }.`when`(it).loadAsset() - } - - addLoader(one, two) - - assertOpenedAsset() - } - - @Test(expected = IllegalStateException::class) - fun throwingNonIOExceptionCausesFailure() { - val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty() - val two = mockLoader { - doAnswer { throw IllegalStateException() }.`when`(it).loadAsset() - } - - addLoader(one, two) - - assertOpenedAsset() - } - - private fun assertOpenedAsset() { - assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt new file mode 100644 index 000000000000..e3ba93d64b0f --- /dev/null +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt @@ -0,0 +1,220 @@ +/* + * 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.content.res.loader.test + +import android.content.res.AssetManager +import android.content.res.loader.AssetsProvider +import android.content.res.loader.DirectoryAssetsProvider +import android.content.res.loader.ResourcesLoader +import android.content.res.loader.ResourcesProvider +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestName +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyString +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.eq +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.mock +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import java.nio.file.Paths + +@RunWith(Parameterized::class) +class ResourceLoaderAssetsTest : ResourceLoaderTestBase() { + + companion object { + private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt" + private const val TEST_TEXT = "some text" + + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun parameters(): Array<Array<out Any?>> { + val fromInputStream: AssetsProvider.(String) -> Any? = { + loadAsset(eq(it), anyInt()) + } + + val fromFileDescriptor: AssetsProvider.(String) -> Any? = { + loadAssetParcelFd(eq(it)) + } + + val openAsset: AssetManager.() -> String? = { + open(BASE_TEST_PATH).reader().readText() + } + + val openNonAsset: AssetManager.() -> String? = { + openNonAssetFd(BASE_TEST_PATH).readText() + } + + return arrayOf( + arrayOf("assets", fromInputStream, openAsset), + arrayOf("", fromFileDescriptor, openNonAsset) + ) + } + } + + @get:Rule + val testName = TestName() + + @JvmField + @field:Parameterized.Parameter(0) + var prefix: String? = null + + @field:Parameterized.Parameter(1) + lateinit var loadAssetFunction: AssetsProvider.(String) -> Any? + + @field:Parameterized.Parameter(2) + lateinit var openAssetFunction: AssetManager.() -> String? + + private val testPath: String + get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString() + + private fun AssetsProvider.loadAsset() = loadAssetFunction(testPath) + + private fun AssetManager.openAsset() = openAssetFunction() + + private lateinit var testDir: File + + @Before + fun setUpTestDir() { + testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}") + testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT) + } + + @Test + fun multipleProvidersSearchesBackwards() { + // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it + val assetsProvider = DirectoryAssetsProvider(testDir) + val assetProviderWrapper = mock(AssetsProvider::class.java).apply { + doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) } + .`when`(this).loadAsset(anyString(), anyInt()) + doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) } + .`when`(this).loadAssetParcelFd(anyString()) + } + + val one = ResourcesProvider.empty(assetProviderWrapper) + val two = mockProvider { + doReturn(null).`when`(it).loadAsset() + } + + val loader = ResourcesLoader() + loader.providers = listOf(one, two) + resources.addLoader(loader) + + assertOpenedAsset() + inOrder(two.assetsProvider, one.assetsProvider).apply { + verify(two.assetsProvider)?.loadAsset() + verify(one.assetsProvider)?.loadAsset() + } + } + + @Test + fun multipleLoadersSearchesBackwards() { + // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it + val assetsProvider = DirectoryAssetsProvider(testDir) + val assetProviderWrapper = mock(AssetsProvider::class.java).apply { + doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) } + .`when`(this).loadAsset(anyString(), anyInt()) + doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) } + .`when`(this).loadAssetParcelFd(anyString()) + } + + val one = ResourcesProvider.empty(assetProviderWrapper) + val two = mockProvider { + doReturn(null).`when`(it).loadAsset() + } + + val loader1 = ResourcesLoader() + loader1.addProvider(one) + val loader2 = ResourcesLoader() + loader2.addProvider(two) + + resources.loaders = listOf(loader1, loader2) + + assertOpenedAsset() + inOrder(two.assetsProvider, one.assetsProvider).apply { + verify(two.assetsProvider)?.loadAsset() + verify(one.assetsProvider)?.loadAsset() + } + } + + @Test(expected = FileNotFoundException::class) + fun failToFindThrowsFileNotFound() { + val assetsProvider1 = mock(AssetsProvider::class.java).apply { + doReturn(null).`when`(this).loadAsset() + } + val assetsProvider2 = mock(AssetsProvider::class.java).apply { + doReturn(null).`when`(this).loadAsset() + } + + val loader = ResourcesLoader() + val one = ResourcesProvider.empty(assetsProvider1) + val two = ResourcesProvider.empty(assetsProvider2) + resources.addLoader(loader) + loader.providers = listOf(one, two) + + assertOpenedAsset() + } + + @Test + fun throwingIOExceptionIsSkipped() { + val assetsProvider1 = DirectoryAssetsProvider(testDir) + val assetsProvider2 = mock(AssetsProvider::class.java).apply { + doAnswer { throw IOException() }.`when`(this).loadAsset() + } + + val loader = ResourcesLoader() + val one = ResourcesProvider.empty(assetsProvider1) + val two = ResourcesProvider.empty(assetsProvider2) + resources.addLoader(loader) + loader.providers = listOf(one, two) + + assertOpenedAsset() + } + + @Test(expected = IllegalStateException::class) + fun throwingNonIOExceptionCausesFailure() { + val assetsProvider1 = DirectoryAssetsProvider(testDir) + val assetsProvider2 = mock(AssetsProvider::class.java).apply { + doAnswer { throw IllegalStateException() }.`when`(this).loadAsset() + } + + val loader = ResourcesLoader() + val one = ResourcesProvider.empty(assetsProvider1) + val two = ResourcesProvider.empty(assetsProvider2) + resources.addLoader(loader) + loader.providers = listOf(one, two) + + assertOpenedAsset() + } + + private fun mockProvider(block: (AssetsProvider) -> Unit = {}): ResourcesProvider { + return ResourcesProvider.empty(mock(AssetsProvider::class.java).apply { + block.invoke(this) + }) + } + + private fun assertOpenedAsset() { + assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT) + } +} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt deleted file mode 100644 index 0c3d34e686dd..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt +++ /dev/null @@ -1,241 +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 android.content.res.loader.test - -import android.app.Activity -import android.app.Instrumentation -import android.app.UiAutomation -import android.content.res.Configuration -import android.content.res.Resources -import android.graphics.Color -import android.os.Bundle -import android.os.ParcelFileDescriptor -import android.widget.FrameLayout -import androidx.test.InstrumentationRegistry -import androidx.test.rule.ActivityTestRule -import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry -import androidx.test.runner.lifecycle.Stage -import com.google.common.truth.Truth.assertThat -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.util.Arrays -import java.util.concurrent.CountDownLatch -import java.util.concurrent.Executor -import java.util.concurrent.FutureTask -import java.util.concurrent.TimeUnit - -@RunWith(Parameterized::class) -class ResourceLoaderChangesTest : ResourceLoaderTestBase() { - - companion object { - private const val TIMEOUT = 30L - private const val OVERLAY_PACKAGE = "android.content.res.loader.test.overlay" - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun data() = arrayOf(DataType.APK, DataType.ARSC) - } - - @field:Parameterized.Parameter(0) - override lateinit var dataType: DataType - - @get:Rule - val activityRule: ActivityTestRule<TestActivity> = - ActivityTestRule<TestActivity>(TestActivity::class.java, false, true) - - // Redirect to the Activity's resources - override val resources: Resources - get() = activityRule.getActivity().resources - - private val activity: TestActivity - get() = activityRule.getActivity() - - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - - @Before - @After - fun disableOverlay() { - enableOverlay(OVERLAY_PACKAGE, false) - } - - @Test - fun activityRecreate() = verifySameBeforeAndAfter { - val oldActivity = activity - var newActivity: Activity? = null - instrumentation.runOnMainSync { oldActivity.recreate() } - instrumentation.waitForIdleSync() - instrumentation.runOnMainSync { - newActivity = ActivityLifecycleMonitorRegistry.getInstance() - .getActivitiesInStage(Stage.RESUMED) - .single() - } - - assertThat(newActivity).isNotNull() - assertThat(newActivity).isNotSameAs(oldActivity) - - // Return the new resources to assert on - return@verifySameBeforeAndAfter newActivity!!.resources - } - - @Test - fun activityHandledOrientationChange() = verifySameBeforeAndAfter { - val latch = CountDownLatch(1) - val oldConfig = Configuration().apply { setTo(resources.configuration) } - var changedConfig: Configuration? = null - - activity.callback = object : TestActivity.Callback { - override fun onConfigurationChanged(newConfig: Configuration) { - changedConfig = newConfig - latch.countDown() - } - } - - val isPortrait = resources.displayMetrics.run { widthPixels < heightPixels } - val newRotation = if (isPortrait) { - UiAutomation.ROTATION_FREEZE_90 - } else { - UiAutomation.ROTATION_FREEZE_0 - } - - instrumentation.uiAutomation.setRotation(newRotation) - - assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue() - assertThat(changedConfig).isNotEqualTo(oldConfig) - return@verifySameBeforeAndAfter activity.resources - } - - @Test - fun enableOverlayCausingPathChange() = verifySameBeforeAndAfter { - assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Not overlaid") - - enableOverlay(OVERLAY_PACKAGE, true) - - assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Overlaid") - - return@verifySameBeforeAndAfter activity.resources - } - - @Test - fun enableOverlayChildContextUnaffected() { - val childContext = activity.createConfigurationContext(Configuration()) - val childResources = childContext.resources - val originalValue = childResources.getString(android.R.string.cancel) - assertThat(childResources.getString(R.string.loader_path_change_test)) - .isEqualTo("Not overlaid") - - verifySameBeforeAndAfter { - enableOverlay(OVERLAY_PACKAGE, true) - return@verifySameBeforeAndAfter activity.resources - } - - // Loader not applied, but overlay change propagated - assertThat(childResources.getString(android.R.string.cancel)).isEqualTo(originalValue) - assertThat(childResources.getString(R.string.loader_path_change_test)) - .isEqualTo("Overlaid") - } - - // All these tests assert for the exact same loaders/values, so extract that logic out - private fun verifySameBeforeAndAfter(block: () -> Resources) { - fun Resources.resource() = this.getString(android.R.string.cancel) - fun Resources.asset() = this.assets.open("Asset.txt").reader().readText() - - val originalResource = resources.resource() - val originalAsset = resources.asset() - - val loaderResource = "stringOne".openLoader() - val loaderAsset = "assetOne".openLoader(dataType = DataType.ASSET) - addLoader(loaderResource) - addLoader(loaderAsset) - - val oldLoaders = resources.loaders - val oldResource = resources.resource() - val oldAsset = resources.asset() - - assertThat(oldResource).isNotEqualTo(originalResource) - assertThat(oldAsset).isNotEqualTo(originalAsset) - - val newResources = block() - - val newLoaders = newResources.loaders - val newResource = newResources.resource() - val newAsset = newResources.asset() - - assertThat(newResource).isEqualTo(oldResource) - assertThat(newAsset).isEqualTo(oldAsset) - assertThat(newLoaders).isEqualTo(oldLoaders) - } - - // Copied from overlaytests LocalOverlayManager - private fun enableOverlay(packageName: String, enable: Boolean) { - val executor = Executor { Thread(it).start() } - val pattern = (if (enable) "[x]" else "[ ]") + " " + packageName - if (executeShellCommand("cmd overlay list").contains(pattern)) { - // nothing to do, overlay already in the requested state - return - } - - val oldApkPaths = resources.assets.apkPaths - val task = FutureTask { - while (true) { - if (!Arrays.equals(oldApkPaths, resources.assets.apkPaths)) { - return@FutureTask true - } - Thread.sleep(10) - } - - @Suppress("UNREACHABLE_CODE") - return@FutureTask false - } - - val command = if (enable) "enable" else "disable" - executeShellCommand("cmd overlay $command $packageName") - executor.execute(task) - assertThat(task.get(TIMEOUT, TimeUnit.SECONDS)).isTrue() - } - - private fun executeShellCommand(command: String): String { - val uiAutomation = instrumentation.uiAutomation - val pfd = uiAutomation.executeShellCommand(command) - return ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.reader().readText() } - } -} - -class TestActivity : Activity() { - - var callback: Callback? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContentView(FrameLayout(this).apply { - setBackgroundColor(Color.BLUE) - }) - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - callback?.onConfigurationChanged(newConfig) - } - - interface Callback { - fun onConfigurationChanged(newConfig: Configuration) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt deleted file mode 100644 index 09fd27e02b59..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt +++ /dev/null @@ -1,183 +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 android.content.res.loader.test - -import android.content.res.Resources -import android.content.res.loader.ResourceLoader -import android.content.res.loader.ResourcesProvider -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import com.google.common.truth.Truth.assertThat -import org.hamcrest.CoreMatchers.not -import org.junit.Assume.assumeThat -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mockito.`when` -import org.mockito.Mockito.any -import org.mockito.Mockito.argThat -import org.mockito.Mockito.eq -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -@RunWith(Parameterized::class) -class ResourceLoaderDrawableTest : ResourceLoaderTestBase() { - - companion object { - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun data() = arrayOf(DataType.APK, DataType.ARSC) - } - - @field:Parameterized.Parameter(0) - override lateinit var dataType: DataType - - @Test - fun matchingConfig() { - val original = getDrawable(android.R.drawable.ic_delete) - val loader = "drawableMdpiWithoutFile".openLoader() - `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any())) - .thenReturn(ColorDrawable(Color.BLUE)) - - addLoader(loader) - - updateConfiguration { densityDpi = 160 /* mdpi */ } - - val drawable = getDrawable(android.R.drawable.ic_delete) - - loader.verifyLoadDrawableCalled() - - assertThat(drawable).isNotEqualTo(original) - assertThat(drawable).isInstanceOf(ColorDrawable::class.java) - assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE) - } - - @Test - fun worseConfig() { - val loader = "drawableMdpiWithoutFile".openLoader() - addLoader(loader) - - updateConfiguration { densityDpi = 480 /* xhdpi */ } - - getDrawable(android.R.drawable.ic_delete) - - verify(loader.first, never()).loadDrawable(any(), anyInt(), anyInt(), any()) - } - - @Test - fun multipleLoaders() { - val original = getDrawable(android.R.drawable.ic_delete) - val loaderOne = "drawableMdpiWithoutFile".openLoader() - val loaderTwo = "drawableMdpiWithoutFile".openLoader() - - `when`(loaderTwo.first.loadDrawable(any(), anyInt(), anyInt(), any())) - .thenReturn(ColorDrawable(Color.BLUE)) - - addLoader(loaderOne, loaderTwo) - - updateConfiguration { densityDpi = 160 /* mdpi */ } - - val drawable = getDrawable(android.R.drawable.ic_delete) - loaderOne.verifyLoadDrawableNotCalled() - loaderTwo.verifyLoadDrawableCalled() - - assertThat(drawable).isNotEqualTo(original) - assertThat(drawable).isInstanceOf(ColorDrawable::class.java) - assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE) - } - - @Test(expected = Resources.NotFoundException::class) - fun multipleLoadersNoReturnWithoutFile() { - val loaderOne = "drawableMdpiWithoutFile".openLoader() - val loaderTwo = "drawableMdpiWithoutFile".openLoader() - - addLoader(loaderOne, loaderTwo) - - updateConfiguration { densityDpi = 160 /* mdpi */ } - - try { - getDrawable(android.R.drawable.ic_delete) - } finally { - // We expect the call to fail because at least the loader won't resolve the overridden - // drawable, but we should still verify that both loaders were called before allowing - // the exception to propagate. - loaderOne.verifyLoadDrawableNotCalled() - loaderTwo.verifyLoadDrawableCalled() - } - } - - @Test - fun multipleLoadersReturnWithFile() { - // Can't return a file if an ARSC - assumeThat(dataType, not(DataType.ARSC)) - - val original = getDrawable(android.R.drawable.ic_delete) - val loaderOne = "drawableMdpiWithFile".openLoader() - val loaderTwo = "drawableMdpiWithFile".openLoader() - - addLoader(loaderOne, loaderTwo) - - updateConfiguration { densityDpi = 160 /* mdpi */ } - - val drawable = getDrawable(android.R.drawable.ic_delete) - loaderOne.verifyLoadDrawableNotCalled() - loaderTwo.verifyLoadDrawableCalled() - - assertThat(drawable).isNotNull() - assertThat(drawable).isInstanceOf(original.javaClass) - } - - @Test - fun unhandledResourceIgnoresLoaders() { - val loader = "drawableMdpiWithoutFile".openLoader() - `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any())) - .thenReturn(ColorDrawable(Color.BLUE)) - addLoader(loader) - - getDrawable(android.R.drawable.ic_menu_add) - - loader.verifyLoadDrawableNotCalled() - - getDrawable(android.R.drawable.ic_delete) - - loader.verifyLoadDrawableCalled() - } - - private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableCalled() { - verify(first).loadDrawable( - argThat { - it.density == 160 && - it.resourceId == android.R.drawable.ic_delete && - it.string == "res/drawable-mdpi-v4/ic_delete.png" - }, - eq(android.R.drawable.ic_delete), - eq(0), - any() - ) - } - - private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableNotCalled() { - verify(first, never()).loadDrawable( - any(), - anyInt(), - anyInt(), - any() - ) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt deleted file mode 100644 index 1ec209486c18..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt +++ /dev/null @@ -1,153 +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 android.content.res.loader.test - -import android.content.res.Resources -import android.content.res.XmlResourceParser -import android.content.res.loader.ResourceLoader -import android.content.res.loader.ResourcesProvider -import com.google.common.truth.Truth.assertThat -import org.hamcrest.CoreMatchers.not -import org.junit.Assume.assumeThat -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mockito.`when` -import org.mockito.Mockito.any -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -@RunWith(Parameterized::class) -class ResourceLoaderLayoutTest : ResourceLoaderTestBase() { - - companion object { - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun data() = arrayOf(DataType.APK, DataType.ARSC) - } - - @field:Parameterized.Parameter(0) - override lateinit var dataType: DataType - - @Test - fun singleLoader() { - val original = getLayout(android.R.layout.activity_list_item) - val mockXml = mock(XmlResourceParser::class.java) - val loader = "layoutWithoutFile".openLoader() - `when`(loader.first.loadXmlResourceParser(any(), anyInt())) - .thenReturn(mockXml) - - addLoader(loader) - - val layout = getLayout(android.R.layout.activity_list_item) - loader.verifyLoadLayoutCalled() - - assertThat(layout).isNotEqualTo(original) - assertThat(layout).isSameAs(mockXml) - } - - @Test - fun multipleLoaders() { - val original = getLayout(android.R.layout.activity_list_item) - val loaderOne = "layoutWithoutFile".openLoader() - val loaderTwo = "layoutWithoutFile".openLoader() - - val mockXml = mock(XmlResourceParser::class.java) - `when`(loaderTwo.first.loadXmlResourceParser(any(), anyInt())) - .thenReturn(mockXml) - - addLoader(loaderOne, loaderTwo) - - val layout = getLayout(android.R.layout.activity_list_item) - loaderOne.verifyLoadLayoutNotCalled() - loaderTwo.verifyLoadLayoutCalled() - - assertThat(layout).isNotEqualTo(original) - assertThat(layout).isSameAs(mockXml) - } - - @Test(expected = Resources.NotFoundException::class) - fun multipleLoadersNoReturnWithoutFile() { - val loaderOne = "layoutWithoutFile".openLoader() - val loaderTwo = "layoutWithoutFile".openLoader() - - addLoader(loaderOne, loaderTwo) - - try { - getLayout(android.R.layout.activity_list_item) - } finally { - // We expect the call to fail because at least one loader must resolve the overridden - // layout, but we should still verify that both loaders were called before allowing - // the exception to propagate. - loaderOne.verifyLoadLayoutNotCalled() - loaderTwo.verifyLoadLayoutCalled() - } - } - - @Test - fun multipleLoadersReturnWithFile() { - // Can't return a file if an ARSC - assumeThat(dataType, not(DataType.ARSC)) - - val loaderOne = "layoutWithFile".openLoader() - val loaderTwo = "layoutWithFile".openLoader() - - addLoader(loaderOne, loaderTwo) - - val xml = getLayout(android.R.layout.activity_list_item) - loaderOne.verifyLoadLayoutNotCalled() - loaderTwo.verifyLoadLayoutCalled() - - assertThat(xml).isNotNull() - } - - @Test - fun unhandledResourceIgnoresLoaders() { - val loader = "layoutWithoutFile".openLoader() - val mockXml = mock(XmlResourceParser::class.java) - `when`(loader.first.loadXmlResourceParser(any(), anyInt())) - .thenReturn(mockXml) - addLoader(loader) - - getLayout(android.R.layout.preference_category) - - verify(loader.first, never()) - .loadXmlResourceParser(anyString(), anyInt()) - - getLayout(android.R.layout.activity_list_item) - - loader.verifyLoadLayoutCalled() - } - - private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutCalled() { - verify(first).loadXmlResourceParser( - "res/layout/activity_list_item.xml", - android.R.layout.activity_list_item - ) - } - - private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutNotCalled() { - verify(first, never()).loadXmlResourceParser( - anyString(), - anyInt() - ) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt index 5af453d526e4..4c62955e41a5 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt @@ -18,25 +18,16 @@ package android.content.res.loader.test import android.content.Context import android.content.res.AssetManager -import android.content.res.Configuration import android.content.res.Resources -import android.content.res.loader.ResourceLoader +import android.content.res.loader.AssetsProvider import android.content.res.loader.ResourcesProvider import android.os.ParcelFileDescriptor -import android.util.TypedValue -import androidx.annotation.DimenRes -import androidx.annotation.DrawableRes -import androidx.annotation.LayoutRes -import androidx.annotation.StringRes import androidx.test.InstrumentationRegistry import org.junit.After import org.junit.Before -import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.argThat import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.doReturn import org.mockito.Mockito.mock import java.io.Closeable @@ -60,7 +51,8 @@ abstract class ResourceLoaderTestBase { @After fun removeAllLoaders() { - resources.setLoaders(null) + resources.clearLoaders() + context.applicationContext.resources.clearLoaders() openedObjects.forEach { try { it.close() @@ -69,149 +61,73 @@ abstract class ResourceLoaderTestBase { } } - protected fun getString(@StringRes stringRes: Int, debugLog: Boolean = false) = - logResolution(debugLog) { getString(stringRes) } - - protected fun getDrawable(@DrawableRes drawableRes: Int, debugLog: Boolean = false) = - logResolution(debugLog) { getDrawable(drawableRes) } - - protected fun getLayout(@LayoutRes layoutRes: Int, debugLog: Boolean = false) = - logResolution(debugLog) { getLayout(layoutRes) } - - protected fun getDimensionPixelSize(@DimenRes dimenRes: Int, debugLog: Boolean = false) = - logResolution(debugLog) { getDimensionPixelSize(dimenRes) } - - private fun <T> logResolution(debugLog: Boolean = false, block: Resources.() -> T): T { - if (debugLog) { - resources.assets.setResourceResolutionLoggingEnabled(true) - } - - var thrown = false - - try { - return resources.block() - } catch (t: Throwable) { - // No good way to log to test output other than throwing an exception - if (debugLog) { - thrown = true - throw IllegalStateException(resources.assets.lastResourceResolution, t) - } else { - throw t - } - } finally { - if (!thrown && debugLog) { - throw IllegalStateException(resources.assets.lastResourceResolution) - } - } - } - - protected fun updateConfiguration(block: Configuration.() -> Unit) { - val configuration = Configuration().apply { - setTo(resources.configuration) - block() - } - - resources.updateConfiguration(configuration, resources.displayMetrics) - } - - protected fun String.openLoader( + protected fun String.openProvider( dataType: DataType = this@ResourceLoaderTestBase.dataType - ): Pair<ResourceLoader, ResourcesProvider> = when (dataType) { + ): ResourcesProvider = when (dataType) { DataType.APK -> { - mock(ResourceLoader::class.java) to context.copiedRawFile("${this}Apk").use { - ResourcesProvider.loadFromApk(it) + context.copiedRawFile("${this}Apk").use { + ResourcesProvider.loadFromApk(it, mock(AssetsProvider::class.java)) }.also { openedObjects += it } } DataType.ARSC -> { - mock(ResourceLoader::class.java) to openArsc(this) + openArsc(this, mock(AssetsProvider::class.java)) } DataType.SPLIT -> { - mock(ResourceLoader::class.java) to ResourcesProvider.loadFromSplit(context, this) + ResourcesProvider.loadFromSplit(context, this) } - DataType.ASSET -> mockLoader { - doAnswer { byteInputStream() }.`when`(it) + DataType.ASSET -> { + val assetsProvider = mock(AssetsProvider::class.java) + doAnswer { byteInputStream() }.`when`(assetsProvider) .loadAsset(eq("assets/Asset.txt"), anyInt()) + ResourcesProvider.empty(assetsProvider) } - DataType.ASSET_FD -> mockLoader { + DataType.ASSET_FD -> { + val assetsProvider = mock(AssetsProvider::class.java) doAnswer { val file = context.filesDir.resolve("Asset.txt") file.writeText(this) ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) - }.`when`(it).loadAssetFd("assets/Asset.txt") + }.`when`(assetsProvider).loadAssetParcelFd("assets/Asset.txt") + ResourcesProvider.empty(assetsProvider) } - DataType.NON_ASSET -> mockLoader { + DataType.NON_ASSET -> { + val assetsProvider = mock(AssetsProvider::class.java) doAnswer { val file = context.filesDir.resolve("NonAsset.txt") file.writeText(this) ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) - }.`when`(it).loadAssetFd("NonAsset.txt") + }.`when`(assetsProvider).loadAssetParcelFd("NonAsset.txt") + ResourcesProvider.empty(assetsProvider) } - DataType.NON_ASSET_DRAWABLE -> mockLoader(openArsc(this)) { - doReturn(null).`when`(it).loadDrawable(argThat { value -> - value.type == TypedValue.TYPE_STRING && - value.resourceId == 0x7f010001 && - value.string == "res/drawable-nodpi-v4/non_asset_drawable.xml" - }, eq(0x7f010001), anyInt(), ArgumentMatchers.any()) - - doAnswer { context.copiedRawFile(this) }.`when`(it) - .loadAssetFd("res/drawable-nodpi-v4/non_asset_drawable.xml") + DataType.NON_ASSET_DRAWABLE -> { + val assetsProvider = mock(AssetsProvider::class.java) + doAnswer { context.copiedRawFile(this) }.`when`(assetsProvider) + .loadAssetParcelFd("res/drawable-nodpi-v4/non_asset_drawable.xml") + openArsc(this, assetsProvider) } - DataType.NON_ASSET_BITMAP -> mockLoader(openArsc(this)) { - doReturn(null).`when`(it).loadDrawable(argThat { value -> - value.type == TypedValue.TYPE_STRING && - value.resourceId == 0x7f010000 && - value.string == "res/drawable-nodpi-v4/non_asset_bitmap.png" - }, eq(0x7f010000), anyInt(), ArgumentMatchers.any()) - - doAnswer { resources.openRawResourceFd(rawFile(this)).createInputStream() } - .`when`(it) + DataType.NON_ASSET_BITMAP -> { + val assetsProvider = mock(AssetsProvider::class.java) + doAnswer { resources.openRawResource(rawFile(this)) } + .`when`(assetsProvider) .loadAsset(eq("res/drawable-nodpi-v4/non_asset_bitmap.png"), anyInt()) + openArsc(this, assetsProvider) } - DataType.NON_ASSET_LAYOUT -> mockLoader(openArsc(this)) { - doReturn(null).`when`(it) - .loadXmlResourceParser("res/layout/layout.xml", 0x7f020000) - - doAnswer { context.copiedRawFile(this) }.`when`(it) - .loadAssetFd("res/layout/layout.xml") + DataType.NON_ASSET_LAYOUT -> { + val assetsProvider = mock(AssetsProvider::class.java) + doAnswer { resources.openRawResource(rawFile(this)) }.`when`(assetsProvider) + .loadAsset(eq("res/layout/layout.xml"), anyInt()) + doAnswer { context.copiedRawFile(this) }.`when`(assetsProvider) + .loadAssetParcelFd("res/layout/layout.xml") + openArsc(this, assetsProvider) } } - protected fun mockLoader( - provider: ResourcesProvider = ResourcesProvider.empty(), - block: (ResourceLoader) -> Unit = {} - ): Pair<ResourceLoader, ResourcesProvider> { - return mock(ResourceLoader::class.java, Utils.ANSWER_THROWS) - .apply(block) to provider - } - - protected fun openArsc(rawName: String): ResourcesProvider { + protected fun openArsc(rawName: String, assetsProvider: AssetsProvider): ResourcesProvider { return context.copiedRawFile("${rawName}Arsc") - .use { ResourcesProvider.loadFromArsc(it) } + .use { ResourcesProvider.loadFromTable(it, assetsProvider) } .also { openedObjects += it } } - // This specifically uses addLoader so both behaviors are tested - protected fun addLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) { - pairs.forEach { resources.addLoader(it.first, it.second) } - } - - protected fun setLoaders(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) { - resources.setLoaders(pairs.map { android.util.Pair(it.first, it.second) }) - } - - protected fun addLoader(pair: Pair<out ResourceLoader, ResourcesProvider>, index: Int) { - resources.addLoader(pair.first, pair.second, index) - } - - protected fun removeLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) { - pairs.forEach { resources.removeLoader(it.first) } - } - - protected fun getLoaders(): MutableList<Pair<ResourceLoader, ResourcesProvider>> { - // Cast instead of toMutableList to maintain the same object - return resources.getLoaders() as MutableList<Pair<ResourceLoader, ResourcesProvider>> - } - enum class DataType { APK, ARSC, diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt index 017552a02152..0cc56d721651 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt @@ -16,15 +16,24 @@ package android.content.res.loader.test +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import android.content.res.Resources +import android.content.res.loader.ResourcesLoader import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable -import com.google.common.truth.Truth.assertThat +import android.os.IBinder +import androidx.test.rule.ActivityTestRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized +import java.util.Collections /** * Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because @@ -36,6 +45,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) class ResourceLoaderValuesTest : ResourceLoaderTestBase() { + @get:Rule + private val mTestActivityRule = ActivityTestRule<TestActivity>(TestActivity::class.java) + companion object { @Parameterized.Parameters(name = "{1} {0}") @JvmStatic @@ -47,14 +59,18 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { getString(android.R.string.cancel) }, "stringOne", { "SomeRidiculouslyUnlikelyStringOne" }, "stringTwo", { "SomeRidiculouslyUnlikelyStringTwo" }, + "stringThree", { "SomeRidiculouslyUnlikelyStringThree" }, + "stringFour", { "SomeRidiculouslyUnlikelyStringFour" }, listOf(DataType.APK, DataType.ARSC) ) // R.dimen parameters += Parameter( - { resources.getDimensionPixelSize(android.R.dimen.app_icon_size) }, - "dimenOne", { 564716.dpToPx(resources) }, - "dimenTwo", { 565717.dpToPx(resources) }, + { getDimensionPixelSize(android.R.dimen.app_icon_size) }, + "dimenOne", { 100.dpToPx(resources) }, + "dimenTwo", { 200.dpToPx(resources) }, + "dimenThree", { 300.dpToPx(resources) }, + "dimenFour", { 400.dpToPx(resources) }, listOf(DataType.APK, DataType.ARSC) ) @@ -63,6 +79,8 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { assets.open("Asset.txt").reader().readText() }, "assetOne", { "assetOne" }, "assetTwo", { "assetTwo" }, + "assetFour", { "assetFour" }, + "assetThree", { "assetThree" }, listOf(DataType.ASSET) ) @@ -71,6 +89,8 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { assets.openFd("Asset.txt").readText() }, "assetOne", { "assetOne" }, "assetTwo", { "assetTwo" }, + "assetFour", { "assetFour" }, + "assetThree", { "assetThree" }, listOf(DataType.ASSET_FD) ) @@ -79,14 +99,18 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { assets.openNonAssetFd("NonAsset.txt").readText() }, "NonAssetOne", { "NonAssetOne" }, "NonAssetTwo", { "NonAssetTwo" }, + "NonAssetThree", { "NonAssetThree" }, + "NonAssetFour", { "NonAssetFour" }, listOf(DataType.NON_ASSET) ) // Asset as compiled XML drawable parameters += Parameter( { (getDrawable(R.drawable.non_asset_drawable) as ColorDrawable).color }, - "nonAssetDrawableOne", { Color.parseColor("#A3C3E3") }, - "nonAssetDrawableTwo", { Color.parseColor("#3A3C3E") }, + "nonAssetDrawableOne", { Color.parseColor("#000001") }, + "nonAssetDrawableTwo", { Color.parseColor("#000002") }, + "nonAssetDrawableThree", { Color.parseColor("#000003") }, + "nonAssetDrawableFour", { Color.parseColor("#000004") }, listOf(DataType.NON_ASSET_DRAWABLE) ) @@ -96,8 +120,10 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { (getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable) .bitmap.getColor(0, 0).toArgb() }, + "nonAssetBitmapRed", { Color.RED }, "nonAssetBitmapGreen", { Color.GREEN }, "nonAssetBitmapBlue", { Color.BLUE }, + "nonAssetBitmapWhite", { Color.WHITE }, listOf(DataType.NON_ASSET_BITMAP) ) @@ -106,6 +132,8 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { getLayout(R.layout.layout).advanceToRoot().name }, "layoutOne", { "RelativeLayout" }, "layoutTwo", { "LinearLayout" }, + "layoutThree", { "FrameLayout" }, + "layoutFour", { "TableLayout" }, listOf(DataType.NON_ASSET_LAYOUT) ) @@ -114,6 +142,8 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { getString(R.string.split_overlaid) }, "split_one", { "Split ONE Overlaid" }, "split_two", { "Split TWO Overlaid" }, + "split_three", { "Split THREE Overlaid" }, + "split_four", { "Split FOUR Overlaid" }, listOf(DataType.SPLIT) ) @@ -134,221 +164,482 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { private val valueOne by lazy { parameter.valueOne(this) } private val valueTwo by lazy { parameter.valueTwo(this) } + private val valueThree by lazy { parameter.valueThree(this) } + private val valueFour by lazy { parameter.valueFour(this) } - private fun openOne() = parameter.loaderOne.openLoader() - private fun openTwo() = parameter.loaderTwo.openLoader() + private fun openOne() = parameter.providerOne.openProvider() + private fun openTwo() = parameter.providerTwo.openProvider() + private fun openThree() = parameter.providerThree.openProvider() + private fun openFour() = parameter.providerFour.openProvider() // Class method for syntax highlighting purposes - private fun getValue() = parameter.getValue(this) + private fun getValue(c: Context = context) = parameter.getValue(c.resources) @Test - fun verifyValueUniqueness() { + fun assertValueUniqueness() { // Ensure the parameters are valid in case of coding errors - assertNotEquals(valueOne, getValue()) - assertNotEquals(valueTwo, getValue()) - assertNotEquals(valueOne, valueTwo) + val original = getValue() + assertNotEquals(valueOne, original) + assertNotEquals(valueTwo, original) + assertNotEquals(valueThree, original) + assertNotEquals(valueFour, original) + assertNotEquals(valueTwo, valueOne) + assertNotEquals(valueThree, valueOne) + assertNotEquals(valueFour, valueOne) + assertNotEquals(valueThree, valueTwo) + assertNotEquals(valueFour, valueTwo) + assertNotEquals(valueFour, valueThree) } @Test - fun addMultipleLoaders() { + fun addMultipleProviders() { val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader = ResourcesLoader() - addLoader(testOne, testTwo) + resources.addLoader(loader) + loader.addProvider(testOne) + assertEquals(valueOne, getValue()) + loader.addProvider(testTwo) assertEquals(valueTwo, getValue()) - removeLoader(testTwo) - - assertEquals(valueOne, getValue()) - - removeLoader(testOne) + loader.removeProvider(testOne) + assertEquals(valueTwo, getValue()) + loader.removeProvider(testTwo) assertEquals(originalValue, getValue()) } @Test - fun setMultipleLoaders() { + fun addMultipleLoaders() { val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader1 = ResourcesLoader() + val loader2 = ResourcesLoader() - setLoaders(testOne, testTwo) + resources.addLoader(loader1) + loader1.addProvider(testOne) + assertEquals(valueOne, getValue()) + resources.addLoader(loader2) + loader2.addProvider(testTwo) assertEquals(valueTwo, getValue()) - removeLoader(testTwo) - - assertEquals(valueOne, getValue()) - - setLoaders() + resources.removeLoader(loader1) + assertEquals(valueTwo, getValue()) + resources.removeLoader(loader2) assertEquals(originalValue, getValue()) } @Test - fun getLoadersContainsAll() { + fun setMultipleProviders() { + val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader = ResourcesLoader() - addLoader(testOne, testTwo) + resources.addLoader(loader) + loader.providers = listOf(testOne, testTwo) + assertEquals(valueTwo, getValue()) - assertThat(getLoaders()).containsAllOf(testOne, testTwo) + loader.removeProvider(testTwo) + assertEquals(valueOne, getValue()) + + loader.providers = Collections.emptyList() + assertEquals(originalValue, getValue()) } @Test - fun getLoadersDoesNotLeakMutability() { + fun setMultipleLoaders() { val originalValue = getValue() - val testOne = openOne() - val testTwo = openTwo() - - addLoader(testOne) - - assertEquals(valueOne, getValue()) + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) - val loaders = getLoaders() - loaders += testTwo + resources.loaders = listOf(loader1, loader2) + assertEquals(valueTwo, getValue()) + resources.removeLoader(loader2) assertEquals(valueOne, getValue()) - removeLoader(testOne) - + resources.loaders = Collections.emptyList() assertEquals(originalValue, getValue()) } - @Test(expected = IllegalArgumentException::class) - fun alreadyAddedThrows() { + @Test(expected = UnsupportedOperationException::class) + fun getProvidersDoesNotLeakMutability() { val testOne = openOne() - val testTwo = openTwo() + val loader = ResourcesLoader() + val providers = loader.providers + providers += testOne + } - addLoader(testOne) - addLoader(testTwo) - addLoader(testOne) + @Test(expected = UnsupportedOperationException::class) + fun getLoadersDoesNotLeakMutability() { + val loaders = resources.loaders + loaders += ResourcesLoader() } - @Test(expected = IllegalArgumentException::class) - fun alreadyAddedAndSetThrows() { + @Test + fun alreadyAddedProviderNoOps() { val testOne = openOne() val testTwo = openTwo() + val loader = ResourcesLoader() + + resources.addLoader(loader) + loader.addProvider(testOne) + loader.addProvider(testTwo) + loader.addProvider(testOne) - addLoader(testOne) - addLoader(testTwo) - setLoaders(testTwo) + assertEquals(2, loader.providers.size) + assertEquals(loader.providers[0], testOne) + assertEquals(loader.providers[1], testTwo) } @Test - fun repeatedRemoveSucceeds() { - val originalValue = getValue() + fun alreadyAddedLoaderNoOps() { + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) + + resources.addLoader(loader1) + resources.addLoader(loader2) + resources.addLoader(loader1) + + assertEquals(2, resources.loaders.size) + assertEquals(resources.loaders[0], loader1) + assertEquals(resources.loaders[1], loader2) + } + + @Test + fun repeatedRemoveProviderNoOps() { val testOne = openOne() + val testTwo = openTwo() + val loader = ResourcesLoader() + + resources.addLoader(loader) + loader.addProvider(testOne) + loader.addProvider(testTwo) - addLoader(testOne) + loader.removeProvider(testOne) + loader.removeProvider(testOne) - assertNotEquals(originalValue, getValue()) + assertEquals(1, loader.providers.size) + assertEquals(loader.providers[0], testTwo) + } - removeLoader(testOne) + @Test + fun repeatedRemoveLoaderNoOps() { + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) + + resources.loaders = listOf(loader1, loader2) + resources.removeLoader(loader1) + resources.removeLoader(loader1) + + assertEquals(1, resources.loaders.size) + assertEquals(resources.loaders[0], loader2) + } - assertEquals(originalValue, getValue()) + @Test + fun repeatedSetProvider() { + val testOne = openOne() + val testTwo = openTwo() + val loader = ResourcesLoader() - removeLoader(testOne) + resources.addLoader(loader) + loader.providers = listOf(testOne, testTwo) + loader.providers = listOf(testOne, testTwo) - assertEquals(originalValue, getValue()) + assertEquals(2, loader.providers.size) + assertEquals(loader.providers[0], testOne) + assertEquals(loader.providers[1], testTwo) + } + + @Test + fun repeatedSetLoaders() { + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) + + resources.loaders = listOf(loader1, loader2) + resources.loaders = listOf(loader1, loader2) + + assertEquals(2, resources.loaders.size) + assertEquals(resources.loaders[0], loader1) + assertEquals(resources.loaders[1], loader2) } @Test - fun addToFront() { + fun reorderProviders() { + val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader = ResourcesLoader() - addLoader(testOne) + resources.addLoader(loader) + loader.addProvider(testOne) + loader.addProvider(testTwo) + assertEquals(valueTwo, getValue()) - assertEquals(valueOne, getValue()) + loader.removeProvider(testOne) + assertEquals(valueTwo, getValue()) - addLoader(testTwo, 0) + loader.addProvider(testOne) + assertEquals(valueOne, getValue()) + loader.removeProvider(testTwo) assertEquals(valueOne, getValue()) - // Remove top loader, so previously added to front should now resolve - removeLoader(testOne) - assertEquals(valueTwo, getValue()) + loader.removeProvider(testOne) + assertEquals(originalValue, getValue()) } @Test - fun addToEnd() { + fun reorderLoaders() { + val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader1 = ResourcesLoader() + loader1.addProvider(testOne) + val loader2 = ResourcesLoader() + loader2.addProvider(testTwo) - addLoader(testOne) + resources.addLoader(loader1) + resources.addLoader(loader2) + assertEquals(valueTwo, getValue()) + resources.removeLoader(loader1) + assertEquals(valueTwo, getValue()) + + resources.addLoader(loader1) assertEquals(valueOne, getValue()) - addLoader(testTwo, 1) + resources.removeLoader(loader2) + assertEquals(valueOne, getValue()) - assertEquals(valueTwo, getValue()) + resources.removeLoader(loader1) + assertEquals(originalValue, getValue()) } - @Test(expected = IndexOutOfBoundsException::class) - fun addPastEnd() { + @Test + fun reorderMultipleLoadersAndProviders() { val testOne = openOne() val testTwo = openTwo() + val testThree = openThree() + val testFour = openFour() - addLoader(testOne) + val loader1 = ResourcesLoader() + loader1.providers = listOf(testOne, testTwo) + val loader2 = ResourcesLoader() + loader2.providers = listOf(testThree, testFour) + + resources.loaders = listOf(loader1, loader2) + assertEquals(valueFour, getValue()) + + resources.loaders = listOf(loader2, loader1) + assertEquals(valueTwo, getValue()) + + loader1.removeProvider(testTwo) assertEquals(valueOne, getValue()) - addLoader(testTwo, 2) + loader1.removeProvider(testOne) + assertEquals(valueFour, getValue()) } - @Test(expected = IndexOutOfBoundsException::class) - fun addBeforeFront() { - val testOne = openOne() - val testTwo = openTwo() + private fun createContext(context: Context, id: Int): Context { + val overrideConfig = Configuration() + overrideConfig.orientation = Int.MAX_VALUE - id + return context.createConfigurationContext(overrideConfig) + } - addLoader(testOne) + @Test + fun copyContextLoaders() { + val originalValue = getValue() + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) + + resources.loaders = listOf(loader1) + assertEquals(valueOne, getValue()) + + // The child context should include the loaders of the original context. + val childContext = createContext(context, 0) + assertEquals(valueOne, getValue(childContext)) + // Changing the loaders of the child context should not affect the original context. + childContext.resources.loaders = listOf(loader1, loader2) assertEquals(valueOne, getValue()) + assertEquals(valueTwo, getValue(childContext)) - addLoader(testTwo, -1) + // Changing the loaders of the original context should not affect the child context. + resources.removeLoader(loader1) + assertEquals(originalValue, getValue()) + assertEquals(valueTwo, getValue(childContext)) + + // A new context created from the original after an update to the original's loaders should + // have the updated loaders. + val originalPrime = createContext(context, 2) + assertEquals(originalValue, getValue(originalPrime)) + + // A new context created from the child context after an update to the child's loaders + // should have the updated loaders. + val childPrime = createContext(childContext, 1) + assertEquals(valueTwo, getValue(childPrime)) } @Test - fun reorder() { + fun loaderUpdatesAffectContexts() { val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader = ResourcesLoader() + + resources.addLoader(loader) + loader.addProvider(testOne) + assertEquals(valueOne, getValue()) - addLoader(testOne, testTwo) + val childContext = createContext(context, 0) + assertEquals(valueOne, getValue(childContext)) + // Adding a provider to a loader affects all contexts that use the loader. + loader.addProvider(testTwo) assertEquals(valueTwo, getValue()) + assertEquals(valueTwo, getValue(childContext)) - removeLoader(testOne) + // Changes to the loaders for a context do not affect providers. + resources.clearLoaders() + assertEquals(originalValue, getValue()) + assertEquals(valueTwo, getValue(childContext)) - assertEquals(valueTwo, getValue()) + val childContext2 = createContext(context, 1) + assertEquals(originalValue, getValue()) + assertEquals(originalValue, getValue(childContext2)) - addLoader(testOne) + childContext2.resources.addLoader(loader) + assertEquals(originalValue, getValue()) + assertEquals(valueTwo, getValue(childContext)) + assertEquals(valueTwo, getValue(childContext2)) + } - assertEquals(valueOne, getValue()) + @Test + fun appLoadersIncludedInActivityContexts() { + val loader = ResourcesLoader() + loader.addProvider(openOne()) + + val applicationContext = context.applicationContext + applicationContext.resources.addLoader(loader) + assertEquals(valueOne, getValue(applicationContext)) - removeLoader(testTwo) + val activity = mTestActivityRule.launchActivity(Intent()) + assertEquals(valueOne, getValue(activity)) + applicationContext.resources.clearLoaders() + } + + @Test + fun loadersApplicationInfoChanged() { + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) + + val applicationContext = context.applicationContext + applicationContext.resources.addLoader(loader1) + assertEquals(valueOne, getValue(applicationContext)) + + var token: IBinder? = null + val activity = mTestActivityRule.launchActivity(Intent()) + mTestActivityRule.runOnUiThread(Runnable { + token = activity.activityToken + val at = activity.activityThread + + // The activity should have the loaders from the application. + assertEquals(valueOne, getValue(applicationContext)) + assertEquals(valueOne, getValue(activity)) + + activity.resources.addLoader(loader2) + assertEquals(valueOne, getValue(applicationContext)) + assertEquals(valueTwo, getValue(activity)) + + // Relaunches the activity. + at.handleApplicationInfoChanged(activity.applicationInfo) + }) + + mTestActivityRule.runOnUiThread(Runnable { + val activityThread = activity.activityThread + val newActivity = activityThread.getActivity(token) + + // The loader added to the activity loaders should not be persisted. + assertEquals(valueOne, getValue(applicationContext)) + assertEquals(valueOne, getValue(newActivity)) + }) + + applicationContext.resources.clearLoaders() + } + + @Test + fun multipleLoadersHaveSameProviders() { + val provider1 = openOne() + val loader1 = ResourcesLoader() + loader1.addProvider(provider1) + val loader2 = ResourcesLoader() + loader2.addProvider(provider1) + loader2.addProvider(openTwo()) + + resources.loaders = listOf(loader1, loader2) + assertEquals(valueTwo, getValue()) + + resources.loaders = listOf(loader2, loader1) assertEquals(valueOne, getValue()) - removeLoader(testOne) + assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader }) + } - assertEquals(originalValue, getValue()) + @Test(expected = IllegalStateException::class) + fun cannotUseClosedProvider() { + val provider = openOne() + provider.close() + val loader = ResourcesLoader() + loader.addProvider(provider) + } + + @Test(expected = IllegalStateException::class) + fun cannotCloseUsedProvider() { + val provider = openOne() + val loader = ResourcesLoader() + loader.addProvider(provider) + provider.close() } data class Parameter( - val getValue: ResourceLoaderValuesTest.() -> Any, - val loaderOne: String, + val getValue: Resources.() -> Any, + val providerOne: String, val valueOne: ResourceLoaderValuesTest.() -> Any, - val loaderTwo: String, + val providerTwo: String, val valueTwo: ResourceLoaderValuesTest.() -> Any, + val providerThree: String, + val valueThree: ResourceLoaderValuesTest.() -> Any, + val providerFour: String, + val valueFour: ResourceLoaderValuesTest.() -> Any, val dataTypes: List<DataType> ) { override fun toString(): String { - val prefix = loaderOne.commonPrefixWith(loaderTwo) - return "$prefix${loaderOne.removePrefix(prefix)}|${loaderTwo.removePrefix(prefix)}" + val prefix = providerOne.commonPrefixWith(providerTwo) + return "$prefix${providerOne.removePrefix(prefix)}|${providerTwo.removePrefix(prefix)}" } } } + +class TestActivity : Activity()
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt index df2d09adf503..4e8ee5cf3c3b 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt @@ -26,10 +26,6 @@ import org.mockito.stubbing.Answer import org.xmlpull.v1.XmlPullParser import java.io.File -// Enforce use of [android.util.Pair] instead of Kotlin's so it matches the ResourceLoader APIs -typealias Pair<F, S> = android.util.Pair<F, S> -infix fun <A, B> A.to(that: B): Pair<A, B> = Pair.create(this, that)!! - object Utils { val ANSWER_THROWS = Answer<Any> { when (val name = it.method.name) { diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 3836d6f4115d..38454ecb6111 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -41,6 +41,7 @@ android_test { "truth-prebuilt", "print-test-util-lib", "testng", + "servicestests-utils" ], libs: [ diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index b85a332e9000..23f9f90248df 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1315,6 +1315,17 @@ android:process=":RedactingProvider"> </provider> + <provider + android:name="android.content.FakeProviderLocal" + android:authorities="android.content.FakeProviderLocal"> + </provider> + + <provider + android:name="android.content.FakeProviderRemote" + android:authorities="android.content.FakeProviderRemote" + android:process=":FakeProvider"> + </provider> + <!-- Application components used for os tests --> <service android:name="android.os.MessengerService" diff --git a/core/tests/coretests/src/android/app/PullAtomMetadataTest.java b/core/tests/coretests/src/android/app/PullAtomMetadataTest.java index 2e3f8928d9c2..0ae613400b18 100644 --- a/core/tests/coretests/src/android/app/PullAtomMetadataTest.java +++ b/core/tests/coretests/src/android/app/PullAtomMetadataTest.java @@ -32,7 +32,7 @@ public final class PullAtomMetadataTest { @Test public void testEmpty() { - PullAtomMetadata metadata = PullAtomMetadata.newBuilder().build(); + PullAtomMetadata metadata = new PullAtomMetadata.Builder().build(); assertThat(metadata.getTimeoutNs()).isEqualTo(StatsManager.DEFAULT_TIMEOUT_NS); assertThat(metadata.getCoolDownNs()).isEqualTo(StatsManager.DEFAULT_COOL_DOWN_NS); assertThat(metadata.getAdditiveFields()).isNull(); @@ -42,7 +42,7 @@ public final class PullAtomMetadataTest { public void testSetTimeoutNs() { long timeoutNs = 500_000_000L; PullAtomMetadata metadata = - PullAtomMetadata.newBuilder().setTimeoutNs(timeoutNs).build(); + new PullAtomMetadata.Builder().setTimeoutNs(timeoutNs).build(); assertThat(metadata.getTimeoutNs()).isEqualTo(timeoutNs); assertThat(metadata.getCoolDownNs()).isEqualTo(StatsManager.DEFAULT_COOL_DOWN_NS); assertThat(metadata.getAdditiveFields()).isNull(); @@ -52,7 +52,7 @@ public final class PullAtomMetadataTest { public void testSetCoolDownNs() { long coolDownNs = 10_000_000_000L; PullAtomMetadata metadata = - PullAtomMetadata.newBuilder().setCoolDownNs(coolDownNs).build(); + new PullAtomMetadata.Builder().setCoolDownNs(coolDownNs).build(); assertThat(metadata.getTimeoutNs()).isEqualTo(StatsManager.DEFAULT_TIMEOUT_NS); assertThat(metadata.getCoolDownNs()).isEqualTo(coolDownNs); assertThat(metadata.getAdditiveFields()).isNull(); @@ -62,7 +62,7 @@ public final class PullAtomMetadataTest { public void testSetAdditiveFields() { int[] fields = {2, 4, 6}; PullAtomMetadata metadata = - PullAtomMetadata.newBuilder().setAdditiveFields(fields).build(); + new PullAtomMetadata.Builder().setAdditiveFields(fields).build(); assertThat(metadata.getTimeoutNs()).isEqualTo(StatsManager.DEFAULT_TIMEOUT_NS); assertThat(metadata.getCoolDownNs()).isEqualTo(StatsManager.DEFAULT_COOL_DOWN_NS); assertThat(metadata.getAdditiveFields()).isEqualTo(fields); @@ -73,7 +73,7 @@ public final class PullAtomMetadataTest { long timeoutNs = 300L; long coolDownNs = 9572L; int[] fields = {3, 2}; - PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setTimeoutNs(timeoutNs) .setCoolDownNs(coolDownNs) .setAdditiveFields(fields) diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java index e140ad2a9a8f..9dcce1e51e0b 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -191,4 +191,22 @@ public class ContentResolverTest { assertEquals(uri, ContentResolver .translateDeprecatedDataPath(ContentResolver.translateDeprecatedDataPath(uri))); } + + @Test + public void testGetType_localProvider() { + // This provider is running in the same process as the test and is already registered with + // the ContentResolver when the application starts, see + // ActivityThread#installContentProviders. This allows ContentResolver to follow a + // streamlined code path. + String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderLocal")); + assertEquals("fake/local", type); + } + + @Test + public void testGetType_remoteProvider() { + // This provider is running in a different process, which will need to be started + // in order to acquire the provider + String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderRemote")); + assertEquals("fake/remote", type); + } } diff --git a/core/tests/coretests/src/android/content/FakeProviderLocal.java b/core/tests/coretests/src/android/content/FakeProviderLocal.java new file mode 100644 index 000000000000..a8c2f4059d50 --- /dev/null +++ b/core/tests/coretests/src/android/content/FakeProviderLocal.java @@ -0,0 +1,57 @@ +/* + * 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 the same process as the test. + */ +public class FakeProviderLocal extends ContentProvider { + + @Override + public boolean onCreate() { + 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 "fake/local"; + } + + @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/FakeProviderRemote.java b/core/tests/coretests/src/android/content/FakeProviderRemote.java new file mode 100644 index 000000000000..7b9bdbcd7a17 --- /dev/null +++ b/core/tests/coretests/src/android/content/FakeProviderRemote.java @@ -0,0 +1,57 @@ +/* + * 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. + */ +public class FakeProviderRemote extends ContentProvider { + + @Override + public boolean onCreate() { + 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 "fake/remote"; + } + + @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 c0d9be5dde59..bf782034c8f1 100644 --- a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java @@ -16,11 +16,12 @@ package android.content.integrity; -import static android.content.integrity.TestUtils.assertExpectException; - import static com.google.common.truth.Truth.assertThat; +import static org.testng.Assert.expectThrows; + import android.content.integrity.AtomicFormula.BooleanAtomicFormula; +import android.content.integrity.AtomicFormula.LongAtomicFormula; import android.content.integrity.AtomicFormula.StringAtomicFormula; import android.os.Parcel; @@ -114,8 +115,8 @@ public class AtomicFormulaTest { @Test public void testValidAtomicFormula_longValue() { - AtomicFormula.LongAtomicFormula longAtomicFormula = - new AtomicFormula.LongAtomicFormula( + LongAtomicFormula longAtomicFormula = + new LongAtomicFormula( AtomicFormula.VERSION_CODE, AtomicFormula.GTE, 1); assertThat(longAtomicFormula.getKey()).isEqualTo(AtomicFormula.VERSION_CODE); @@ -133,35 +134,38 @@ public class AtomicFormulaTest { @Test public void testInvalidAtomicFormula_stringValue() { - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex */ - String.format("Key VERSION_CODE cannot be used with StringAtomicFormula"), - () -> - new StringAtomicFormula( - AtomicFormula.VERSION_CODE, - "test-value", - /* isHashedValue= */ false)); + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> + new StringAtomicFormula( + AtomicFormula.VERSION_CODE, + "test-value", + /* isHashedValue= */ false)); + assertThat(e.getMessage()).matches( + "Key VERSION_CODE cannot be used with StringAtomicFormula"); } @Test public void testInvalidAtomicFormula_longValue() { - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex */ - String.format("Key PACKAGE_NAME cannot be used with LongAtomicFormula"), - () -> - new AtomicFormula.LongAtomicFormula( - AtomicFormula.PACKAGE_NAME, AtomicFormula.EQ, 1)); + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> + new LongAtomicFormula( + AtomicFormula.PACKAGE_NAME, AtomicFormula.EQ, 1)); + assertThat(e.getMessage()).matches( + "Key PACKAGE_NAME cannot be used with LongAtomicFormula"); } @Test public void testInvalidAtomicFormula_boolValue() { - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex */ - String.format("Key PACKAGE_NAME cannot be used with BooleanAtomicFormula"), - () -> new BooleanAtomicFormula(AtomicFormula.PACKAGE_NAME, true)); + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> new BooleanAtomicFormula(AtomicFormula.PACKAGE_NAME, true)); + assertThat(e.getMessage()).matches( + "Key PACKAGE_NAME cannot be used with BooleanAtomicFormula"); } @Test @@ -179,14 +183,14 @@ public class AtomicFormulaTest { @Test public void testParcelUnparcel_int() { - AtomicFormula.LongAtomicFormula formula = - new AtomicFormula.LongAtomicFormula( + LongAtomicFormula formula = + new LongAtomicFormula( AtomicFormula.VERSION_CODE, AtomicFormula.GT, 1); Parcel p = Parcel.obtain(); formula.writeToParcel(p, 0); p.setDataPosition(0); - AtomicFormula.LongAtomicFormula newFormula = - AtomicFormula.LongAtomicFormula.CREATOR.createFromParcel(p); + LongAtomicFormula newFormula = + LongAtomicFormula.CREATOR.createFromParcel(p); assertThat(newFormula).isEqualTo(formula); } @@ -205,20 +209,24 @@ public class AtomicFormulaTest { @Test public void testInvalidAtomicFormula_invalidKey() { - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex */ "Unknown key: -1", - () -> new AtomicFormula.LongAtomicFormula(/* key= */ -1, AtomicFormula.EQ, 0)); + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> new LongAtomicFormula(/* key= */ -1, + AtomicFormula.EQ, /* value= */0)); + assertThat(e.getMessage()).matches("Unknown key: -1"); } @Test public void testInvalidAtomicFormula_invalidOperator() { - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex */ "Unknown operator: -1", - () -> - new AtomicFormula.LongAtomicFormula( - AtomicFormula.VERSION_CODE, /* operator= */ -1, 0)); + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> + new LongAtomicFormula( + AtomicFormula.VERSION_CODE, /* operator= */ -1, /* value= */ + 0)); + assertThat(e.getMessage()).matches("Unknown operator: -1"); } @Test @@ -245,10 +253,59 @@ public class AtomicFormulaTest { assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse(); } + + @Test + public void testIsAppCertificateFormula_string_true() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, "cert", /* isHashedValue= */false); + + assertThat(stringAtomicFormula.isAppCertificateFormula()).isTrue(); + } + + @Test + public void testIsAppCertificateFormula_string_false() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, "com.test.app", /* isHashedValue= */ + false); + + assertThat(stringAtomicFormula.isAppCertificateFormula()).isFalse(); + } + + @Test + public void testIsInstallerFormula_string_false() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, "cert", /* isHashedValue= */false); + + assertThat(stringAtomicFormula.isInstallerFormula()).isFalse(); + } + + @Test + public void testIsInstallerFormula_string_installerName_true() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.INSTALLER_NAME, + "com.test.installer", + /* isHashedValue= */false); + + assertThat(stringAtomicFormula.isInstallerFormula()).isTrue(); + } + + @Test + public void testIsInstallerFormula_string_installerCertificate_true() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.INSTALLER_CERTIFICATE, "cert", /* isHashedValue= */false); + + assertThat(stringAtomicFormula.isInstallerFormula()).isTrue(); + } + @Test public void testFormulaMatches_long_eq_true() { - AtomicFormula.LongAtomicFormula longAtomicFormula = - new AtomicFormula.LongAtomicFormula( + LongAtomicFormula longAtomicFormula = + new LongAtomicFormula( AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 0); AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder().setVersionCode(0).build(); @@ -258,8 +315,8 @@ public class AtomicFormulaTest { @Test public void testFormulaMatches_long_eq_false() { - AtomicFormula.LongAtomicFormula longAtomicFormula = - new AtomicFormula.LongAtomicFormula( + LongAtomicFormula longAtomicFormula = + new LongAtomicFormula( AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 0); AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder().setVersionCode(1).build(); @@ -269,8 +326,8 @@ public class AtomicFormulaTest { @Test public void testFormulaMatches_long_gt_true() { - AtomicFormula.LongAtomicFormula longAtomicFormula = - new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, + LongAtomicFormula longAtomicFormula = + new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, 0); AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder().setVersionCode(1).build(); @@ -280,8 +337,8 @@ public class AtomicFormulaTest { @Test public void testFormulaMatches_long_gt_false() { - AtomicFormula.LongAtomicFormula longAtomicFormula = - new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, + LongAtomicFormula longAtomicFormula = + new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, 1); AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder().setVersionCode(0).build(); @@ -291,8 +348,8 @@ public class AtomicFormulaTest { @Test public void testFormulaMatches_long_gte_true() { - AtomicFormula.LongAtomicFormula longAtomicFormula = - new AtomicFormula.LongAtomicFormula( + LongAtomicFormula longAtomicFormula = + new LongAtomicFormula( AtomicFormula.VERSION_CODE, AtomicFormula.GTE, 1); AppInstallMetadata appInstallMetadata1 = @@ -306,8 +363,8 @@ public class AtomicFormulaTest { @Test public void testFormulaMatches_long_gte_false() { - AtomicFormula.LongAtomicFormula longAtomicFormula = - new AtomicFormula.LongAtomicFormula( + LongAtomicFormula longAtomicFormula = + new LongAtomicFormula( AtomicFormula.VERSION_CODE, AtomicFormula.GTE, 1); AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder().setVersionCode(0).build(); @@ -316,6 +373,24 @@ public class AtomicFormulaTest { } @Test + public void testIsAppCertificateFormula_long_false() { + LongAtomicFormula longAtomicFormula = + new AtomicFormula.LongAtomicFormula( + AtomicFormula.VERSION_CODE, AtomicFormula.GTE, 1); + + assertThat(longAtomicFormula.isAppCertificateFormula()).isFalse(); + } + + @Test + public void testIsInstallerFormula_long_false() { + LongAtomicFormula longAtomicFormula = + new LongAtomicFormula( + AtomicFormula.VERSION_CODE, AtomicFormula.GTE, 1); + + assertThat(longAtomicFormula.isInstallerFormula()).isFalse(); + } + + @Test public void testFormulaMatches_bool_true() { BooleanAtomicFormula boolFormula = new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); @@ -335,6 +410,22 @@ public class AtomicFormulaTest { assertThat(boolFormula.matches(appInstallMetadata)).isFalse(); } + @Test + public void testIsAppCertificateFormula_bool_false() { + BooleanAtomicFormula boolFormula = + new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); + + assertThat(boolFormula.isAppCertificateFormula()).isFalse(); + } + + @Test + public void testIsInstallerFormula_bool_false() { + BooleanAtomicFormula boolFormula = + new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); + + assertThat(boolFormula.isInstallerFormula()).isFalse(); + } + /** Returns a builder with all fields filled with some dummy data. */ private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { return new AppInstallMetadata.Builder() diff --git a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java index fa3d671c09e5..f47dfdd191fb 100644 --- a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java @@ -16,11 +16,9 @@ package android.content.integrity; -import static android.content.integrity.TestUtils.assertExpectException; - import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; +import static org.testng.Assert.expectThrows; import android.os.Parcel; @@ -46,32 +44,32 @@ public class CompoundFormulaTest { new CompoundFormula( CompoundFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); - assertEquals(CompoundFormula.AND, compoundFormula.getConnector()); - assertEquals( - Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2), compoundFormula.getFormulas()); + assertThat(compoundFormula.getConnector()).isEqualTo(CompoundFormula.AND); + assertThat(compoundFormula.getFormulas()).containsAllOf(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2); } @Test public void testValidateAuxiliaryFormula_binaryConnectors() { - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex */ - "Connector AND must have at least 2 formulas", - () -> - new CompoundFormula( - CompoundFormula.AND, Collections.singletonList(ATOMIC_FORMULA_1))); + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> + new CompoundFormula( + CompoundFormula.AND, + Collections.singletonList(ATOMIC_FORMULA_1))); + assertThat(e.getMessage()).matches("Connector AND must have at least 2 formulas"); } @Test public void testValidateAuxiliaryFormula_unaryConnectors() { - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex */ - "Connector NOT must have 1 formula only", - () -> - new CompoundFormula( - CompoundFormula.NOT, - Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2))); + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> + new CompoundFormula( + CompoundFormula.NOT, + Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2))); + assertThat(e.getMessage()).matches("Connector NOT must have 1 formula only"); } @Test @@ -82,20 +80,20 @@ public class CompoundFormulaTest { Parcel p = Parcel.obtain(); formula.writeToParcel(p, 0); p.setDataPosition(0); - CompoundFormula newFormula = CompoundFormula.CREATOR.createFromParcel(p); - assertEquals(formula, newFormula); + assertThat(CompoundFormula.CREATOR.createFromParcel(p)).isEqualTo(formula); } @Test public void testInvalidCompoundFormula_invalidConnector() { - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex */ "Unknown connector: -1", - () -> - new CompoundFormula( - /* connector= */ -1, - Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2))); + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> + new CompoundFormula( + /* connector= */ -1, + Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2))); + assertThat(e.getMessage()).matches("Unknown connector: -1"); } @Test @@ -227,6 +225,63 @@ public class CompoundFormulaTest { assertThat(compoundFormula.matches(appInstallMetadata)).isFalse(); } + @Test + public void testIsAppCertificateFormula_false() { + CompoundFormula compoundFormula = + new CompoundFormula( + CompoundFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + + assertThat(compoundFormula.isAppCertificateFormula()).isFalse(); + } + + @Test + public void testIsAppCertificateFormula_true() { + AtomicFormula appCertFormula = + new AtomicFormula.StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, + "app.cert", /* isHashed= */false); + CompoundFormula compoundFormula = + new CompoundFormula( + CompoundFormula.AND, + Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2, appCertFormula)); + + assertThat(compoundFormula.isAppCertificateFormula()).isTrue(); + } + + @Test + public void testIsInstallerFormula_false() { + CompoundFormula compoundFormula = + new CompoundFormula( + CompoundFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)); + + assertThat(compoundFormula.isInstallerFormula()).isFalse(); + } + + @Test + public void testIsInstallerFormula_installerName_true() { + AtomicFormula installerNameFormula = + new AtomicFormula.StringAtomicFormula(AtomicFormula.INSTALLER_NAME, + "com.test.installer", /* isHashed= */false); + CompoundFormula compoundFormula = + new CompoundFormula( + CompoundFormula.AND, + Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2, installerNameFormula)); + + assertThat(compoundFormula.isInstallerFormula()).isTrue(); + } + + @Test + public void testIsInstallerFormula_installerCertificate_true() { + AtomicFormula installerCertificateFormula = + new AtomicFormula.StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE, + "cert", /* isHashed= */false); + CompoundFormula compoundFormula = + new CompoundFormula( + CompoundFormula.AND, Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2, + installerCertificateFormula)); + + assertThat(compoundFormula.isInstallerFormula()).isTrue(); + } + /** Returns a builder with all fields filled with some dummy data. */ private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { return new AppInstallMetadata.Builder() diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityUtilsTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityUtilsTest.java index 639adf6a05d7..e6f6686dcb6f 100644 --- a/core/tests/coretests/src/android/content/integrity/IntegrityUtilsTest.java +++ b/core/tests/coretests/src/android/content/integrity/IntegrityUtilsTest.java @@ -18,6 +18,7 @@ package android.content.integrity; import static com.google.common.truth.Truth.assertThat; +import static org.testng.Assert.expectThrows; import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals; import org.junit.Test; @@ -43,15 +44,20 @@ public class IntegrityUtilsTest { } @Test - public void testInvalidHexDigest() { - TestUtils.assertExpectException( - IllegalArgumentException.class, - "must have even length", - () -> IntegrityUtils.getBytesFromHexDigest("ABC")); - - TestUtils.assertExpectException( - IllegalArgumentException.class, - "Invalid hex char", - () -> IntegrityUtils.getBytesFromHexDigest("GH")); + public void testInvalidHexDigest_mustHaveEvenLength() { + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> IntegrityUtils.getBytesFromHexDigest("ABC")); + assertThat(e.getMessage()).containsMatch("must have even length"); + } + + @Test + public void testInvalidHexDigest_invalidHexChar() { + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> IntegrityUtils.getBytesFromHexDigest("GH")); + assertThat(e.getMessage()).containsMatch("Invalid hex char"); } } diff --git a/core/tests/coretests/src/android/content/integrity/RuleTest.java b/core/tests/coretests/src/android/content/integrity/RuleTest.java index 8c4cfca39e90..8faf8ba2f52a 100644 --- a/core/tests/coretests/src/android/content/integrity/RuleTest.java +++ b/core/tests/coretests/src/android/content/integrity/RuleTest.java @@ -16,10 +16,9 @@ package android.content.integrity; -import static android.content.integrity.TestUtils.assertExpectException; +import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import static org.testng.Assert.expectThrows; import android.os.Parcel; @@ -50,16 +49,16 @@ public class RuleTest { public void testValidRule() { Rule validRule = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT); - assertEquals(PACKAGE_NAME_ATOMIC_FORMULA, validRule.getFormula()); - assertEquals(DENY_EFFECT, validRule.getEffect()); + assertThat(validRule.getFormula()).isEqualTo(PACKAGE_NAME_ATOMIC_FORMULA); + assertThat(validRule.getEffect()).isEqualTo(DENY_EFFECT); } @Test public void testInvalidRule_invalidFormula() { - assertExpectException( - NullPointerException.class, - /* expectedExceptionMessageRegex */ null, - () -> new Rule(null, DENY_EFFECT)); + Exception e = + expectThrows( + NullPointerException.class, + () -> new Rule(null, DENY_EFFECT)); } @Test @@ -70,27 +69,23 @@ public class RuleTest { Arrays.asList(PACKAGE_NAME_ATOMIC_FORMULA, APP_CERTIFICATE_ATOMIC_FORMULA)); Rule rule = new Rule(compoundFormula, Rule.DENY); - assertEquals( - String.format( - "Rule: (PACKAGE_NAME EQ %s) AND (APP_CERTIFICATE EQ %s), DENY", - PACKAGE_NAME, APP_CERTIFICATE), - rule.toString()); + assertThat(rule.toString()) + .isEqualTo( + String.format( + "Rule: (PACKAGE_NAME EQ %s) AND (APP_CERTIFICATE EQ %s), DENY", + PACKAGE_NAME, APP_CERTIFICATE)); } @Test public void testEquals_trueCase() { - Rule rule1 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT); - Rule rule2 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT); - - assertEquals(rule1, rule2); + assertThat(new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT)) + .isEqualTo(new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT)); } @Test public void testEquals_falseCase() { - Rule rule1 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT); - Rule rule2 = new Rule(APP_CERTIFICATE_ATOMIC_FORMULA, DENY_EFFECT); - - assertNotEquals(rule1, rule2); + assertThat(new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT)) + .isNotEqualTo(new Rule(APP_CERTIFICATE_ATOMIC_FORMULA, DENY_EFFECT)); } @Test @@ -108,16 +103,16 @@ public class RuleTest { Parcel p = Parcel.obtain(); rule.writeToParcel(p, 0); p.setDataPosition(0); - Rule newRule = Rule.CREATOR.createFromParcel(p); - assertEquals(newRule, rule); + assertThat(Rule.CREATOR.createFromParcel(p)).isEqualTo(rule); } @Test public void testInvalidRule_invalidEffect() { - assertExpectException( - IllegalArgumentException.class, - /* expectedExceptionMessageRegex */ "Unknown effect: -1", - () -> new Rule(PACKAGE_NAME_ATOMIC_FORMULA, /* effect= */ -1)); + Exception e = + expectThrows( + IllegalArgumentException.class, + () -> new Rule(PACKAGE_NAME_ATOMIC_FORMULA, /* effect= */ -1)); + assertThat(e.getMessage()).isEqualTo("Unknown effect: -1"); } } diff --git a/core/tests/coretests/src/android/content/integrity/TestUtils.java b/core/tests/coretests/src/android/content/integrity/TestUtils.java deleted file mode 100644 index af984cf318f5..000000000000 --- a/core/tests/coretests/src/android/content/integrity/TestUtils.java +++ /dev/null @@ -1,51 +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 android.content.integrity; - -import android.test.MoreAsserts; - -import junit.framework.Assert; - -/** Helper methods used in tests. */ -class TestUtils { - private TestUtils() {} - - public interface ExceptionRunnable { - void run() throws Exception; - } - - public static void assertExpectException( - Class<? extends Throwable> expectedExceptionType, - String expectedExceptionMessageRegex, - ExceptionRunnable r) { - try { - r.run(); - } catch (Throwable e) { - Assert.assertTrue( - "Expected exception type was " - + expectedExceptionType.getName() - + " but caught " - + e.getClass().getName(), - expectedExceptionType.isAssignableFrom(e.getClass())); - if (expectedExceptionMessageRegex != null) { - MoreAsserts.assertContainsRegex(expectedExceptionMessageRegex, e.getMessage()); - } - return; // Pass. - } - Assert.fail( - "Expected exception type " + expectedExceptionType.getName() + " was not thrown"); - } -} diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java index a2dab996a8d7..df5c9d2df90b 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java @@ -72,12 +72,12 @@ public class ResourcesManagerTest extends TestCase { public void testMultipleCallsWithIdenticalParametersCacheReference() { Resources resources = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources); Resources newResources = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(newResources); assertSame(resources, newResources); } @@ -86,14 +86,14 @@ public class ResourcesManagerTest extends TestCase { public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() { Resources resources = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources); Configuration overrideConfig = new Configuration(); overrideConfig.smallestScreenWidthDp = 200; Resources newResources = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, overrideConfig, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(newResources); assertNotSame(resources, newResources); } @@ -102,12 +102,13 @@ public class ResourcesManagerTest extends TestCase { public void testAddingASplitCreatesANewImpl() { Resources resources1 = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Resources resources2 = mResourcesManager.getResources( null, APP_ONE_RES_DIR, new String[] { APP_ONE_RES_SPLIT_DIR }, null, null, - Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO,null, + null); assertNotNull(resources2); assertNotSame(resources1, resources2); @@ -118,12 +119,12 @@ public class ResourcesManagerTest extends TestCase { public void testUpdateConfigurationUpdatesAllAssetManagers() { Resources resources1 = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Resources resources2 = mResourcesManager.getResources( null, APP_TWO_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources2); Binder activity = new Binder(); @@ -131,7 +132,7 @@ public class ResourcesManagerTest extends TestCase { overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE; Resources resources3 = mResourcesManager.getResources( activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, - overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources3); // No Resources object should be the same. @@ -164,13 +165,13 @@ public class ResourcesManagerTest extends TestCase { Binder activity1 = new Binder(); Resources resources1 = mResourcesManager.getResources( activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Binder activity2 = new Binder(); Resources resources2 = mResourcesManager.getResources( activity2, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); // The references themselves should be unique. @@ -185,7 +186,7 @@ public class ResourcesManagerTest extends TestCase { Binder activity1 = new Binder(); Resources resources1 = mResourcesManager.createBaseActivityResources( activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Resources.Theme theme = resources1.newTheme(); @@ -218,7 +219,7 @@ public class ResourcesManagerTest extends TestCase { config1.densityDpi = 280; Resources resources1 = mResourcesManager.createBaseActivityResources( activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config1, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); // Create a Resources based on the Activity. @@ -226,7 +227,7 @@ public class ResourcesManagerTest extends TestCase { config2.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES; Resources resources2 = mResourcesManager.getResources( activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config2, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources2); assertNotSame(resources1, resources2); diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java index 3dc998709b8f..7338c3a3ea8a 100644 --- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java +++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java @@ -145,9 +145,6 @@ public class AdaptiveIconDrawableTest extends AndroidTestCase { assertEquals("top", boundFromDrawable.top, boundFromDeviceConfig.top, delta); assertEquals("right", boundFromDrawable.right, boundFromDeviceConfig.right, delta); assertEquals("bottom", boundFromDrawable.bottom, boundFromDeviceConfig.bottom, delta); - - assertTrue("path from device config is convex.", pathFromDeviceConfig.isConvex()); - assertTrue("path from drawable is convex.", pathFromDrawable.isConvex()); } @Test @@ -169,8 +166,6 @@ public class AdaptiveIconDrawableTest extends AndroidTestCase { assertEquals("top", top, maskBounds.top, delta); assertEquals("right", right, maskBounds.right, delta); assertEquals("bottom", bottom, maskBounds.bottom, delta); - - assertTrue(mIconDrawable.getIconMask().isConvex()); } @Test @@ -185,7 +180,6 @@ public class AdaptiveIconDrawableTest extends AndroidTestCase { mIconDrawable.setBounds(left, top, right, bottom); Outline outline = new Outline(); mIconDrawable.getOutline(outline); - assertTrue("outline path should be convex", outline.mPath.isConvex()); } @Test diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java new file mode 100644 index 000000000000..4212ef21e0af --- /dev/null +++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java @@ -0,0 +1,232 @@ +/* + * 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.provider; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.ContentProvider; +import android.content.IContentProvider; +import android.os.Bundle; +import android.platform.test.annotations.Presubmit; +import android.test.mock.MockContentResolver; +import android.util.MemoryIntArray; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * These tests are verifying that Settings#NameValueCache is working as expected with DeviceConfig. + * Due to how the classes are structured, we have to test it in a somewhat roundabout way. We're + * mocking out the contentProvider and are handcrafting very specific Bundles to answer the queries. + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NameValueCacheTest { + + private static final String NAMESPACE = "namespace"; + private static final String NAMESPACE2 = "namespace2"; + + @Mock + private IContentProvider mMockIContentProvider; + @Mock + private ContentProvider mMockContentProvider; + private MockContentResolver mMockContentResolver; + private MemoryIntArray mCacheGenerationStore; + private int mCurrentGeneration = 123; + + private HashMap<String, HashMap<String, String>> mStorage; + + @Before + public void setUp() throws Exception { + Settings.Config.clearProviderForTest(); + MockitoAnnotations.initMocks(this); + when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider); + mMockContentResolver = new MockContentResolver(); + mMockContentResolver.addProvider(DeviceConfig.CONTENT_URI.getAuthority(), + mMockContentProvider); + mCacheGenerationStore = new MemoryIntArray(1); + mStorage = new HashMap<>(); + + // Stores keyValues for a given prefix and increments the generation. (Note that this + // increments the generation no matter what, it doesn't pay attention to if anything + // actually changed). + when(mMockIContentProvider.call(any(), any(), eq(DeviceConfig.CONTENT_URI.getAuthority()), + eq(Settings.CALL_METHOD_SET_ALL_CONFIG), + any(), any(Bundle.class))).thenAnswer(invocationOnMock -> { + Bundle incomingBundle = invocationOnMock.getArgument(5); + HashMap<String, String> keyValues = + (HashMap<String, String>) incomingBundle.getSerializable( + Settings.CALL_METHOD_FLAGS_KEY); + String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY); + mStorage.put(prefix, keyValues); + mCacheGenerationStore.set(0, ++mCurrentGeneration); + + Bundle result = new Bundle(); + result.putBoolean(Settings.KEY_CONFIG_SET_RETURN, true); + return result; + }); + + // Returns the keyValues corresponding to a namespace, or an empty map if the namespace + // doesn't have anything stored for it. Returns the generation key if the caller asked + // for one. + when(mMockIContentProvider.call(any(), any(), eq(DeviceConfig.CONTENT_URI.getAuthority()), + eq(Settings.CALL_METHOD_LIST_CONFIG), + any(), any(Bundle.class))).thenAnswer(invocationOnMock -> { + Bundle incomingBundle = invocationOnMock.getArgument(5); + + String prefix = incomingBundle.getString(Settings.CALL_METHOD_PREFIX_KEY); + + if (!mStorage.containsKey(prefix)) { + mStorage.put(prefix, new HashMap<>()); + } + HashMap<String, String> keyValues = mStorage.get(prefix); + + Bundle bundle = new Bundle(); + bundle.putSerializable(Settings.NameValueTable.VALUE, keyValues); + + if (incomingBundle.containsKey(Settings.CALL_METHOD_TRACK_GENERATION_KEY)) { + bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY, + mCacheGenerationStore); + bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, 0); + bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY, + mCacheGenerationStore.get(0)); + } + return bundle; + }); + } + + @Test + public void testCaching_singleNamespace() throws Exception { + HashMap<String, String> keyValues = new HashMap<>(); + keyValues.put("a", "b"); + Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues); + verify(mMockIContentProvider).call(any(), any(), any(), + eq(Settings.CALL_METHOD_SET_ALL_CONFIG), + any(), any(Bundle.class)); + + Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver, + NAMESPACE, + Collections.emptyList()); + verify(mMockIContentProvider).call(any(), any(), any(), + eq(Settings.CALL_METHOD_LIST_CONFIG), + any(), any(Bundle.class)); + assertThat(returnedValues).containsExactlyEntriesIn(keyValues); + + Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver, + NAMESPACE, Collections.emptyList()); + verifyNoMoreInteractions(mMockIContentProvider); + assertThat(cachedKeyValues).containsExactlyEntriesIn(keyValues); + + // Modify the value to invalidate the cache. + keyValues.put("a", "c"); + Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues); + verify(mMockIContentProvider, times(2)).call(any(), any(), any(), + eq(Settings.CALL_METHOD_SET_ALL_CONFIG), + any(), any(Bundle.class)); + + Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver, + NAMESPACE, Collections.emptyList()); + verify(mMockIContentProvider, times(2)).call(any(), any(), any(), + eq(Settings.CALL_METHOD_LIST_CONFIG), + any(), any(Bundle.class)); + assertThat(returnedValues2).containsExactlyEntriesIn(keyValues); + + Map<String, String> cachedKeyValues2 = Settings.Config.getStrings(mMockContentResolver, + NAMESPACE, Collections.emptyList()); + verifyNoMoreInteractions(mMockIContentProvider); + assertThat(cachedKeyValues2).containsExactlyEntriesIn(keyValues); + } + + @Test + public void testCaching_multipleNamespaces() throws Exception { + HashMap<String, String> keyValues = new HashMap<>(); + keyValues.put("a", "b"); + Settings.Config.setStrings(mMockContentResolver, NAMESPACE, keyValues); + verify(mMockIContentProvider).call(any(), any(), any(), + eq(Settings.CALL_METHOD_SET_ALL_CONFIG), + any(), any(Bundle.class)); + + HashMap<String, String> keyValues2 = new HashMap<>(); + keyValues2.put("c", "d"); + keyValues2.put("e", "f"); + Settings.Config.setStrings(mMockContentResolver, NAMESPACE2, keyValues2); + verify(mMockIContentProvider, times(2)).call(any(), any(), any(), + eq(Settings.CALL_METHOD_SET_ALL_CONFIG), + any(), any(Bundle.class)); + + Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver, + NAMESPACE, + Collections.emptyList()); + verify(mMockIContentProvider).call(any(), any(), any(), + eq(Settings.CALL_METHOD_LIST_CONFIG), + any(), any(Bundle.class)); + assertThat(returnedValues).containsExactlyEntriesIn(keyValues); + + Map<String, String> returnedValues2 = Settings.Config.getStrings(mMockContentResolver, + NAMESPACE2, + Collections.emptyList()); + verify(mMockIContentProvider, times(2)).call(any(), any(), any(), + eq(Settings.CALL_METHOD_LIST_CONFIG), + any(), any(Bundle.class)); + assertThat(returnedValues2).containsExactlyEntriesIn(keyValues2); + + Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver, + NAMESPACE, Collections.emptyList()); + verifyNoMoreInteractions(mMockIContentProvider); + assertThat(cachedKeyValues).containsExactlyEntriesIn(keyValues); + + Map<String, String> cachedKeyValues2 = Settings.Config.getStrings(mMockContentResolver, + NAMESPACE2, Collections.emptyList()); + verifyNoMoreInteractions(mMockIContentProvider); + assertThat(cachedKeyValues2).containsExactlyEntriesIn(keyValues2); + } + + @Test + public void testCaching_emptyNamespace() throws Exception { + Map<String, String> returnedValues = Settings.Config.getStrings(mMockContentResolver, + NAMESPACE, + Collections.emptyList()); + verify(mMockIContentProvider).call(any(), any(), any(), + eq(Settings.CALL_METHOD_LIST_CONFIG), + any(), any(Bundle.class)); + assertThat(returnedValues).isEmpty(); + + Map<String, String> cachedKeyValues = Settings.Config.getStrings(mMockContentResolver, + NAMESPACE, Collections.emptyList()); + verifyNoMoreInteractions(mMockIContentProvider); + assertThat(cachedKeyValues).isEmpty(); + } + +} diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index bcf0b8ce4439..169716f99dea 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -176,8 +176,6 @@ public class InsetsAnimationControlImplTest { when(mMockController.getState()).thenReturn(mInsetsState); mController.finish(true /* shown */); mController.applyChangeInsets(mInsetsState); - assertTrue(mInsetsState.getSource(ITYPE_STATUS_BAR).isVisible()); - assertTrue(mInsetsState.getSource(ITYPE_NAVIGATION_BAR).isVisible()); assertEquals(Insets.of(0, 100, 100, 0), mController.getCurrentInsets()); verify(mMockController).notifyFinished(eq(mController), eq(true /* shown */)); } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index f720c987a525..e68c4b8d2ab3 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -19,10 +19,13 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_HIDE; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.ANIMATION_TYPE_SHOW; +import static android.view.InsetsSourceConsumer.ShowResult.IME_SHOW_DELAYED; +import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; @@ -32,6 +35,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -41,6 +46,7 @@ import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type; import android.view.WindowManager.BadTokenException; import android.view.WindowManager.LayoutParams; @@ -48,6 +54,9 @@ import android.view.animation.LinearInterpolator; import android.view.test.InsetsModeSession; import android.widget.TextView; +import com.android.server.testutils.OffsettableClock; +import com.android.server.testutils.TestHandler; + import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -59,6 +68,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.concurrent.CountDownLatch; +import java.util.function.Supplier; /** * Tests for {@link InsetsController}. @@ -77,6 +87,8 @@ public class InsetsControllerTest { private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; private ViewRootImpl mViewRoot; + private TestHandler mTestHandler; + private OffsettableClock mTestClock; private static InsetsModeSession sInsetsModeSession; @BeforeClass @@ -103,7 +115,26 @@ public class InsetsControllerTest { } catch (BadTokenException e) { // activity isn't running, we will ignore BadTokenException. } - mController = new InsetsController(mViewRoot); + mTestClock = new OffsettableClock(); + mTestHandler = new TestHandler(null, mTestClock); + mController = new InsetsController(mViewRoot, (controller, type) -> { + if (type == ITYPE_IME) { + return new InsetsSourceConsumer(type, controller.getState(), + Transaction::new, controller) { + @Override + public int requestShow(boolean fromController) { + if (fromController) { + return SHOW_IMMEDIATELY; + } else { + return IME_SHOW_DELAYED; + } + } + }; + } else { + return new InsetsSourceConsumer(type, controller.getState(), Transaction::new, + controller); + } + }, mTestHandler); final Rect rect = new Rect(5, 5, 5, 5); mController.getState().getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10)); mController.getState().getSource(ITYPE_NAVIGATION_BAR).setFrame( @@ -395,6 +426,71 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } + @Test + public void testControlImeNotReady() { + prepareControls(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlInputMethodAnimation(0, new LinearInterpolator(), listener); + + // Ready gets deferred until next predraw + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + + verify(listener, never()).onReady(any(), anyInt()); + + // Pretend that IME is calling. + mController.show(ime(), true); + + // Ready gets deferred until next predraw + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + + verify(listener).onReady(notNull(), eq(ime())); + + }); + } + + @Test + public void testControlImeNotReady_controlRevoked() { + prepareControls(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlInputMethodAnimation(0, new LinearInterpolator(), listener); + + // Ready gets deferred until next predraw + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + + verify(listener, never()).onReady(any(), anyInt()); + + // Pretend that we are losing control + mController.onControlsChanged(new InsetsSourceControl[0]); + + verify(listener).onCancelled(); + }); + } + + @Test + public void testControlImeNotReady_timeout() { + prepareControls(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + WindowInsetsAnimationControlListener listener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlInputMethodAnimation(0, new LinearInterpolator(), listener); + + // Ready gets deferred until next predraw + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + + verify(listener, never()).onReady(any(), anyInt()); + + // Pretend that timeout is happening + mTestClock.fastForward(2500); + mTestHandler.timeAdvance(); + + verify(listener).onCancelled(); + }); + } + private void waitUntilNextFrame() throws Exception { final CountDownLatch latch = new CountDownLatch(1); Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT, diff --git a/core/tests/coretests/src/android/view/KeyEventTest.java b/core/tests/coretests/src/android/view/KeyEventTest.java index 88d3bdbe3c92..14999c7f5642 100644 --- a/core/tests/coretests/src/android/view/KeyEventTest.java +++ b/core/tests/coretests/src/android/view/KeyEventTest.java @@ -20,8 +20,10 @@ import static android.view.Display.INVALID_DISPLAY; import static org.junit.Assert.assertEquals; +import android.os.Parcel; + +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +42,7 @@ public class KeyEventTest { private static final int SCAN_CODE = 0; private static final int FLAGS = 0; private static final int SOURCE = InputDevice.SOURCE_KEYBOARD; + private static final byte[] HMAC = null; private static final String CHARACTERS = null; @Test @@ -65,25 +68,15 @@ public class KeyEventTest { KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, CHARACTERS); KeyEvent keyEvent2 = KeyEvent.obtain(keyEvent); - assertEquals(keyEvent.getDownTime(), keyEvent2.getDownTime()); - assertEquals(keyEvent.getEventTime(), keyEvent2.getEventTime()); - assertEquals(keyEvent.getAction(), keyEvent2.getAction()); - assertEquals(keyEvent.getKeyCode(), keyEvent2.getKeyCode()); - assertEquals(keyEvent.getRepeatCount(), keyEvent2.getRepeatCount()); - assertEquals(keyEvent.getMetaState(), keyEvent2.getMetaState()); - assertEquals(keyEvent.getDeviceId(), keyEvent2.getDeviceId()); - assertEquals(keyEvent.getScanCode(), keyEvent2.getScanCode()); - assertEquals(keyEvent.getFlags(), keyEvent2.getFlags()); - assertEquals(keyEvent.getSource(), keyEvent2.getSource()); - assertEquals(keyEvent.getDisplayId(), keyEvent2.getDisplayId()); - assertEquals(keyEvent.getCharacters(), keyEvent2.getCharacters()); + compareKeys(keyEvent, keyEvent2); } @Test public void testObtainWithDisplayId() { final int displayId = 5; KeyEvent keyEvent = KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, - METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, displayId, CHARACTERS); + METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, displayId, null /* hmac*/, + CHARACTERS); assertEquals(DOWN_TIME, keyEvent.getDownTime()); assertEquals(EVENT_TIME, keyEvent.getEventTime()); assertEquals(ACTION, keyEvent.getAction()); @@ -97,4 +90,44 @@ public class KeyEventTest { assertEquals(displayId, keyEvent.getDisplayId()); assertEquals(CHARACTERS, keyEvent.getCharacters()); } + + @Test + public void testParcelUnparcel() { + KeyEvent key1 = createKey(); + Parcel parcel = Parcel.obtain(); + key1.writeToParcel(parcel, 0 /*flags*/); + parcel.setDataPosition(0); + + KeyEvent key2 = KeyEvent.CREATOR.createFromParcel(parcel); + parcel.recycle(); + + compareKeys(key1, key2); + } + + @Test + public void testConstructor() { + KeyEvent key1 = createKey(); + KeyEvent key2 = new KeyEvent(key1); + compareKeys(key1, key2); + } + + private static KeyEvent createKey() { + return KeyEvent.obtain(DOWN_TIME, EVENT_TIME, ACTION, KEYCODE, REPEAT, + METASTATE, DEVICE_ID, SCAN_CODE, FLAGS, SOURCE, INVALID_DISPLAY, HMAC, CHARACTERS); + } + + private static void compareKeys(KeyEvent key1, KeyEvent key2) { + assertEquals(key1.getDownTime(), key2.getDownTime()); + assertEquals(key1.getEventTime(), key2.getEventTime()); + assertEquals(key1.getAction(), key2.getAction()); + assertEquals(key1.getKeyCode(), key2.getKeyCode()); + assertEquals(key1.getRepeatCount(), key2.getRepeatCount()); + assertEquals(key1.getMetaState(), key2.getMetaState()); + assertEquals(key1.getDeviceId(), key2.getDeviceId()); + assertEquals(key1.getScanCode(), key2.getScanCode()); + assertEquals(key1.getFlags(), key2.getFlags()); + assertEquals(key1.getSource(), key2.getSource()); + assertEquals(key1.getDisplayId(), key2.getDisplayId()); + assertEquals(key1.getCharacters(), key2.getCharacters()); + } } diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml index 720489834494..cf0199bd1b85 100644 --- a/data/etc/car/com.android.car.developeroptions.xml +++ b/data/etc/car/com.android.car.developeroptions.xml @@ -51,6 +51,7 @@ <permission name="android.permission.USER_ACTIVITY"/> <permission name="android.permission.WRITE_APN_SETTINGS"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> </privapp-permissions> </permissions> diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml index 70b61e0e2552..a200a510cf12 100644 --- a/data/etc/com.android.settings.xml +++ b/data/etc/com.android.settings.xml @@ -49,6 +49,7 @@ <permission name="android.permission.USER_ACTIVITY"/> <permission name="android.permission.WRITE_APN_SETTINGS"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/> diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index 40de83aefb18..5d2e303b921e 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -58,6 +58,7 @@ <permission name="android.permission.WATCH_APPOPS"/> <permission name="android.permission.WRITE_DREAM_STATE"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/> <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 133b1aed68f1..aabd4125d0ed 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -59,6 +59,7 @@ applications that come with the platform <privapp-permissions package="com.android.externalstorage"> <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> </privapp-permissions> <privapp-permissions package="com.android.location.fused"> @@ -98,6 +99,7 @@ applications that come with the platform <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> </privapp-permissions> <privapp-permissions package="com.android.musicfx"> @@ -201,6 +203,7 @@ applications that come with the platform <permission name="android.permission.CONNECTIVITY_INTERNAL"/> <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.UPDATE_DEVICE_STATS"/> </privapp-permissions> @@ -210,6 +213,7 @@ applications that come with the platform <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.USE_RESERVED_DISK"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> <permission name="android.permission.WATCH_APPOPS"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.UPDATE_DEVICE_STATS"/> @@ -266,6 +270,7 @@ applications that come with the platform <privapp-permissions package="com.android.sharedstoragebackup"> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> </privapp-permissions> <privapp-permissions package="com.android.shell"> @@ -341,6 +346,7 @@ applications that come with the platform <permission name="android.permission.UPDATE_APP_OPS_STATS"/> <permission name="android.permission.USE_RESERVED_DISK"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> + <permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/> <permission name="android.permission.WRITE_SECURE_SETTINGS"/> <permission name="android.permission.STATUS_BAR_SERVICE"/> <permission name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL"/> diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 9a0ca3e4ad9b..d949444d44d6 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1162,6 +1162,7 @@ public class Canvas extends BaseCanvas { * @see #quickReject(float, float, float, float, EdgeType) * @see #quickReject(Path, EdgeType) * @see #quickReject(RectF, EdgeType) + * @deprecated quickReject no longer uses this. */ public enum EdgeType { @@ -1197,13 +1198,30 @@ public class Canvas extends BaseCanvas { * non-antialiased ({@link Canvas.EdgeType#BW}). * @return true if the rect (transformed by the canvas' matrix) * does not intersect with the canvas' clip + * @deprecated The EdgeType is ignored. Use {@link #quickReject(RectF)} instead. */ + @Deprecated public boolean quickReject(@NonNull RectF rect, @NonNull EdgeType type) { return nQuickReject(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom); } /** + * Return true if the specified rectangle, after being transformed by the + * current matrix, would lie completely outside of the current clip. Call + * this to check if an area you intend to draw into is clipped out (and + * therefore you can skip making the draw calls). + * + * @param rect the rect to compare with the current clip + * @return true if the rect (transformed by the canvas' matrix) + * does not intersect with the canvas' clip + */ + public boolean quickReject(@NonNull RectF rect) { + return nQuickReject(mNativeCanvasWrapper, + rect.left, rect.top, rect.right, rect.bottom); + } + + /** * Return true if the specified path, after being transformed by the * current matrix, would lie completely outside of the current clip. Call * this to check if an area you intend to draw into is clipped out (and @@ -1217,12 +1235,30 @@ public class Canvas extends BaseCanvas { * non-antialiased ({@link Canvas.EdgeType#BW}). * @return true if the path (transformed by the canvas' matrix) * does not intersect with the canvas' clip + * @deprecated The EdgeType is ignored. Use {@link #quickReject(Path)} instead. */ + @Deprecated public boolean quickReject(@NonNull Path path, @NonNull EdgeType type) { return nQuickReject(mNativeCanvasWrapper, path.readOnlyNI()); } /** + * Return true if the specified path, after being transformed by the + * current matrix, would lie completely outside of the current clip. Call + * this to check if an area you intend to draw into is clipped out (and + * therefore you can skip making the draw calls). Note: for speed it may + * return false even if the path itself might not intersect the clip + * (i.e. the bounds of the path intersects, but the path does not). + * + * @param path The path to compare with the current clip + * @return true if the path (transformed by the canvas' matrix) + * does not intersect with the canvas' clip + */ + public boolean quickReject(@NonNull Path path) { + return nQuickReject(mNativeCanvasWrapper, path.readOnlyNI()); + } + + /** * Return true if the specified rectangle, after being transformed by the * current matrix, would lie completely outside of the current clip. Call * this to check if an area you intend to draw into is clipped out (and @@ -1241,13 +1277,37 @@ public class Canvas extends BaseCanvas { * non-antialiased ({@link Canvas.EdgeType#BW}). * @return true if the rect (transformed by the canvas' matrix) * does not intersect with the canvas' clip + * @deprecated The EdgeType is ignored. Use {@link #quickReject(float, float, float, float)} + * instead. */ + @Deprecated public boolean quickReject(float left, float top, float right, float bottom, @NonNull EdgeType type) { return nQuickReject(mNativeCanvasWrapper, left, top, right, bottom); } /** + * Return true if the specified rectangle, after being transformed by the + * current matrix, would lie completely outside of the current clip. Call + * this to check if an area you intend to draw into is clipped out (and + * therefore you can skip making the draw calls). + * + * @param left The left side of the rectangle to compare with the + * current clip + * @param top The top of the rectangle to compare with the current + * clip + * @param right The right side of the rectangle to compare with the + * current clip + * @param bottom The bottom of the rectangle to compare with the + * current clip + * @return true if the rect (transformed by the canvas' matrix) + * does not intersect with the canvas' clip + */ + public boolean quickReject(float left, float top, float right, float bottom) { + return nQuickReject(mNativeCanvasWrapper, left, top, right, bottom); + } + + /** * Return the bounds of the current clip (in local coordinates) in the * bounds parameter, and return true if it is non-empty. This can be useful * in a way similar to quickReject, in that it tells you that drawing diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index 1362fd864d29..84fe39290681 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -209,7 +209,13 @@ public class Path { * points, and cache the result. * * @return True if the path is convex. + * + * @deprecated This method is not reliable. The way convexity is computed may change from + * release to release, and convexity could change based on a matrix as well. This method was + * useful when non-convex Paths were unable to be used in certain contexts, but that is no + * longer the case. */ + @Deprecated public boolean isConvex() { return nIsConvex(mNativePath); } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index eae13d0062e8..dd01243629c3 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -79,6 +79,11 @@ public final class MediaRoute2Info implements Parcelable { */ public static final int CONNECTION_STATE_CONNECTED = 2; + /** @hide */ + @IntDef({PLAYBACK_VOLUME_FIXED, PLAYBACK_VOLUME_VARIABLE}) + @Retention(RetentionPolicy.SOURCE) + public @interface PlaybackVolume {} + /** * Playback information indicating the playback volume is fixed, i.e. it cannot be * controlled from this object. An example of fixed playback volume is a remote player, @@ -326,6 +331,7 @@ public final class MediaRoute2Info implements Parcelable { * * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE} */ + @PlaybackVolume public int getVolumeHandling() { return mVolumeHandling; } @@ -375,6 +381,7 @@ public final class MediaRoute2Info implements Parcelable { * * @param features the list of route features to consider * @return true if the route has at least one feature in the list + * @hide */ public boolean hasAnyFeatures(@NonNull Collection<String> features) { Objects.requireNonNull(features, "features must not be null"); @@ -548,6 +555,12 @@ public final class MediaRoute2Info implements Parcelable { /** * Adds a feature for the route. + * @param feature a feature that the route has. May be one of predefined features + * such as {@link #FEATURE_LIVE_AUDIO}, {@link #FEATURE_LIVE_VIDEO} or + * {@link #FEATURE_REMOTE_PLAYBACK} or a custom feature defined by + * a provider. + * + * @see #addFeatures(Collection) */ @NonNull public Builder addFeature(@NonNull String feature) { @@ -560,6 +573,12 @@ public final class MediaRoute2Info implements Parcelable { /** * Adds features for the route. A route must support at least one route type. + * @param features features that the route has. May include predefined features + * such as {@link #FEATURE_LIVE_AUDIO}, {@link #FEATURE_LIVE_VIDEO} or + * {@link #FEATURE_REMOTE_PLAYBACK} or custom features defined by + * a provider. + * + * @see #addFeature(String) */ @NonNull public Builder addFeatures(@NonNull Collection<String> features) { @@ -643,7 +662,7 @@ public final class MediaRoute2Info implements Parcelable { * Sets the route's volume handling. */ @NonNull - public Builder setVolumeHandling(int volumeHandling) { + public Builder setVolumeHandling(@PlaybackVolume int volumeHandling) { mVolumeHandling = volumeHandling; return this; } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 1e8b18808891..e39b7bc59148 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -21,6 +21,7 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainMessa import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; import android.app.Service; import android.content.Intent; import android.os.Binder; @@ -57,6 +58,11 @@ import java.util.concurrent.atomic.AtomicBoolean; public abstract class MediaRoute2ProviderService extends Service { private static final String TAG = "MR2ProviderService"; + /** + * The {@link Intent} action that must be declared as handled by the service. + * Put this in your manifest to provide media routes. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; /** diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 18670e9a6fe2..7b9a44f71473 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -49,10 +49,10 @@ import java.util.stream.Collectors; /** * Media Router 2 allows applications to control the routing of media channels * and streams from the current device to remote speakers and devices. - * */ // TODO: Add method names at the beginning of log messages. (e.g. updateControllerOnHandler) // Not only MediaRouter2, but also to service / manager / provider. +// TODO: ensure thread-safe and document it public class MediaRouter2 { private static final String TAG = "MR2"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -159,7 +159,8 @@ public class MediaRouter2 { /** * Registers a callback to discover routes and to receive events when they change. * <p> - * If you register the same callback twice or more, it will be ignored. + * If the specified callback is already registered, its registration will be updated for the + * given {@link Executor executor} and {@link RouteDiscoveryPreference discovery preference}. * </p> */ public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor, @@ -170,10 +171,11 @@ public class MediaRouter2 { Objects.requireNonNull(preference, "preference must not be null"); RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference); - if (!mRouteCallbackRecords.addIfAbsent(record)) { - Log.w(TAG, "Ignoring the same callback"); - return; - } + + mRouteCallbackRecords.remove(record); + // It can fail to add the callback record if another registration with the same callback + // is happening but it's okay because either this or the other registration should be done. + mRouteCallbackRecords.addIfAbsent(record); synchronized (sRouterLock) { if (mClient == null) { diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java index 612f83a14e12..60b3fc642ee4 100644 --- a/media/java/android/media/audiopolicy/AudioProductStrategy.java +++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java @@ -134,7 +134,9 @@ public final class AudioProductStrategy implements Parcelable { + "DO NOT USE STREAM TO CONTROL THE VOLUME"); return AudioSystem.STREAM_MUSIC; } - return streamType; + if (streamType < AudioSystem.getNumStreamTypes()) { + return streamType; + } } } return AudioSystem.STREAM_MUSIC; diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index c199c6f8b761..508a46f492dd 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -89,7 +89,7 @@ interface ITvInputManager { void timeShiftEnablePositionTracking(in IBinder sessionToken, boolean enable, int userId); // For the recording session - void startRecording(in IBinder sessionToken, in Uri programUri, int userId); + void startRecording(in IBinder sessionToken, in Uri programUri, in Bundle params, int userId); void stopRecording(in IBinder sessionToken, int userId); // For TV input hardware binding diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 356ec3cd1f11..24b87d50b33e 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -56,6 +56,6 @@ oneway interface ITvInputSession { void timeShiftEnablePositionTracking(boolean enable); // For the recording session - void startRecording(in Uri programUri); + void startRecording(in Uri programUri, in Bundle params); void stopRecording(); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index 07cfbda7ac2b..e89d33d70d5c 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -216,7 +216,8 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand break; } case DO_START_RECORDING: { - mTvInputRecordingSessionImpl.startRecording((Uri) msg.obj); + SomeArgs args = (SomeArgs) msg.obj; + mTvInputRecordingSessionImpl.startRecording((Uri) args.arg1, (Bundle) args.arg2); break; } case DO_STOP_RECORDING: { @@ -352,8 +353,9 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } @Override - public void startRecording(@Nullable Uri programUri) { - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_RECORDING, programUri)); + public void startRecording(@Nullable Uri programUri, @Nullable Bundle params) { + mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_RECORDING, programUri, + params)); } @Override diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index bb867630ca01..2589521ca370 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -2381,12 +2381,23 @@ public final class TvInputManager { * {@link TvContract#buildProgramUri(long)}. Can be {@code null}. */ void startRecording(@Nullable Uri programUri) { + startRecording(programUri, null); + } + + /** + * Starts TV program recording in the current recording session. + * + * @param programUri The URI for the TV program to record as a hint, built by + * {@link TvContract#buildProgramUri(long)}. Can be {@code null}. + * @param params A set of extra parameters which might be handled with this event. + */ + void startRecording(@Nullable Uri programUri, @Nullable Bundle params) { if (mToken == null) { Log.w(TAG, "The session has been already released"); return; } try { - mService.startRecording(mToken, programUri, mUserId); + mService.startRecording(mToken, programUri, params, mUserId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 7e1f44cb0899..62c7e51f3edf 100755 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1818,6 +1818,30 @@ public abstract class TvInputService extends Service { public abstract void onStartRecording(@Nullable Uri programUri); /** + * Called when the application requests to start TV program recording. Recording must start + * immediately when this method is called. + * + * <p>The application may supply the URI for a TV program for filling in program specific + * data fields in the {@link android.media.tv.TvContract.RecordedPrograms} table. + * A non-null {@code programUri} implies the started recording should be of that specific + * program, whereas null {@code programUri} does not impose such a requirement and the + * recording can span across multiple TV programs. In either case, the application must call + * {@link TvRecordingClient#stopRecording()} to stop the recording. + * + * <p>The session must call {@link #notifyError(int)} if the start request cannot be + * fulfilled. + * + * @param programUri The URI for the TV program to record, built by + * {@link TvContract#buildProgramUri(long)}. Can be {@code null}. + * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped + * name, i.e. prefixed with a package name you own, so that different developers + * will not create conflicting keys. + */ + public void onStartRecording(@Nullable Uri programUri, @NonNull Bundle params) { + onStartRecording(programUri); + } + + /** * Called when the application requests to stop TV program recording. Recording must stop * immediately when this method is called. * @@ -1867,11 +1891,11 @@ public abstract class TvInputService extends Service { } /** - * Calls {@link #onStartRecording(Uri)}. + * Calls {@link #onStartRecording(Uri, Bundle)}. * */ - void startRecording(@Nullable Uri programUri) { - onStartRecording(programUri); + void startRecording(@Nullable Uri programUri, @NonNull Bundle params) { + onStartRecording(programUri, params); } /** diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java index 5aadeb6ecfa9..8ae98ae5e937 100644 --- a/media/java/android/media/tv/TvRecordingClient.java +++ b/media/java/android/media/tv/TvRecordingClient.java @@ -170,11 +170,37 @@ public class TvRecordingClient { * @throws IllegalStateException If {@link #tune} request hasn't been handled yet. */ public void startRecording(@Nullable Uri programUri) { + startRecording(programUri, Bundle.EMPTY); + } + + /** + * Starts TV program recording in the current recording session. Recording is expected to start + * immediately when this method is called. If the current recording session has not yet tuned to + * any channel, this method throws an exception. + * + * <p>The application may supply the URI for a TV program for filling in program specific data + * fields in the {@link android.media.tv.TvContract.RecordedPrograms} table. + * A non-null {@code programUri} implies the started recording should be of that specific + * program, whereas null {@code programUri} does not impose such a requirement and the + * recording can span across multiple TV programs. In either case, the application must call + * {@link TvRecordingClient#stopRecording()} to stop the recording. + * + * <p>The recording session will respond by calling {@link RecordingCallback#onError(int)} if + * the start request cannot be fulfilled. + * + * @param programUri The URI for the TV program to record, built by + * {@link TvContract#buildProgramUri(long)}. Can be {@code null}. + * @param params Domain-specific data for this request. Keys <em>must</em> be a scoped + * name, i.e. prefixed with a package name you own, so that different developers will + * not create conflicting keys. + * @throws IllegalStateException If {@link #tune} request hasn't been handled yet. + */ + public void startRecording(@Nullable Uri programUri, @NonNull Bundle params) { if (!mIsTuned) { throw new IllegalStateException("startRecording failed - not yet tuned"); } if (mSession != null) { - mSession.startRecording(programUri); + mSession.startRecording(programUri, params); mIsRecordingStarted = true; } } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 4f1125f5e482..007d367a533a 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -314,7 +314,7 @@ jobject JTuner::openLnbById(int id) { JNIEnv *env = AndroidRuntime::getJNIEnv(); return env->NewObject( - env->FindClass("android/media/tv/tuner/Tuner$Lnb"), + env->FindClass("android/media/tv/tuner/Lnb"), gFields.lnbInitID, mObject, id); @@ -373,7 +373,7 @@ jobject JTuner::openDescrambler() { JNIEnv *env = AndroidRuntime::getJNIEnv(); jobject descramblerObj = env->NewObject( - env->FindClass("android/media/tv/tuner/Tuner$Descrambler"), + env->FindClass("android/media/tv/tuner/Descrambler"), gFields.descramblerInitID, mObject); @@ -408,7 +408,7 @@ jobject JTuner::openFilter(DemuxFilterType type, int bufferSize) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jobject filterObj = env->NewObject( - env->FindClass("android/media/tv/tuner/Tuner$Filter"), + env->FindClass("android/media/tv/tuner/filter/Filter"), gFields.filterInitID, mObject, (jint) fId); @@ -443,7 +443,7 @@ jobject JTuner::openDvr(DvrType type, int bufferSize) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jobject dvrObj = env->NewObject( - env->FindClass("android/media/tv/tuner/Tuner$Dvr"), + env->FindClass("android/media/tv/tuner/dvr/Dvr"), gFields.dvrInitID, mObject); sp<Dvr> dvrSp = new Dvr(iDvrSp, dvrObj); @@ -495,14 +495,14 @@ static DemuxPid getDemuxPid(int pidType, int pid) { static FrontendSettings getFrontendSettings(JNIEnv *env, int type, jobject settings) { FrontendSettings frontendSettings; - jclass clazz = env->FindClass("android/media/tv/tuner/FrontendSettings"); + jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendSettings"); jfieldID freqField = env->GetFieldID(clazz, "mFrequency", "I"); uint32_t freq = static_cast<uint32_t>(env->GetIntField(clazz, freqField)); // TODO: handle the other 8 types of settings if (type == 1) { // analog - clazz = env->FindClass("android/media/tv/tuner/FrontendSettings$FrontendAnalogSettings"); + clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendSettings"); FrontendAnalogType analogType = static_cast<FrontendAnalogType>( env->GetIntField(settings, env->GetFieldID(clazz, "mAnalogType", "I"))); @@ -525,7 +525,7 @@ static sp<Filter> getFilter(JNIEnv *env, jobject filter) { static DvrSettings getDvrSettings(JNIEnv *env, jobject settings) { DvrSettings dvrSettings; - jclass clazz = env->FindClass("android/media/tv/tuner/DvrSettings"); + jclass clazz = env->FindClass("android/media/tv/tuner/dvr/DvrSettings"); uint32_t statusMask = static_cast<uint32_t>(env->GetIntField( settings, env->GetFieldID(clazz, "mStatusMask", "I"))); @@ -585,23 +585,23 @@ static void android_media_tv_Tuner_native_init(JNIEnv *env) { gFields.frontendInitID = env->GetMethodID(frontendClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V"); - jclass lnbClazz = env->FindClass("android/media/tv/tuner/Tuner$Lnb"); + jclass lnbClazz = env->FindClass("android/media/tv/tuner/Lnb"); gFields.lnbInitID = env->GetMethodID(lnbClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V"); - jclass filterClazz = env->FindClass("android/media/tv/tuner/Tuner$Filter"); + jclass filterClazz = env->FindClass("android/media/tv/tuner/filter/Filter"); gFields.filterContext = env->GetFieldID(filterClazz, "mNativeContext", "J"); gFields.filterInitID = env->GetMethodID(filterClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;I)V"); gFields.onFilterStatusID = env->GetMethodID(filterClazz, "onFilterStatus", "(I)V"); - jclass descramblerClazz = env->FindClass("android/media/tv/tuner/Tuner$Descrambler"); + jclass descramblerClazz = env->FindClass("android/media/tv/tuner/Descrambler"); gFields.descramblerContext = env->GetFieldID(descramblerClazz, "mNativeContext", "J"); gFields.descramblerInitID = env->GetMethodID(descramblerClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;)V"); - jclass dvrClazz = env->FindClass("android/media/tv/tuner/Tuner$Dvr"); + jclass dvrClazz = env->FindClass("android/media/tv/tuner/dvr/Dvr"); gFields.dvrContext = env->GetFieldID(dvrClazz, "mNativeContext", "J"); gFields.dvrInitID = env->GetMethodID(dvrClazz, "<init>", "(Landroid/media/tv/tuner/Tuner;)V"); } @@ -649,7 +649,7 @@ static int android_media_tv_Tuner_set_lna(JNIEnv*, jobject, jint, jboolean) { return 0; } -static jobjectArray android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) { +static jobject android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) { return NULL; } @@ -684,7 +684,7 @@ static jobject android_media_tv_Tuner_open_lnb_by_id(JNIEnv *env, jobject thiz, } static jobject android_media_tv_Tuner_open_filter( - JNIEnv *env, jobject thiz, jint type, jint subType, jint bufferSize) { + JNIEnv *env, jobject thiz, jint type, jint subType, jlong bufferSize) { sp<JTuner> tuner = getTuner(env, thiz); DemuxFilterType filterType { .mainType = static_cast<DemuxFilterMainType>(type), @@ -708,17 +708,17 @@ static DemuxFilterSettings getFilterSettings( env->GetObjectField( filterSettingsObj, env->GetFieldID( - env->FindClass("android/media/tv/tuner/FilterSettings"), + env->FindClass("android/media/tv/tuner/filter/FilterConfiguration"), "mSettings", - "Landroid/media/tv/tuner/FilterSettings$Settings;")); + "Landroid/media/tv/tuner/filter/Settings;")); if (type == (int)DemuxFilterMainType::TS) { // DemuxTsFilterSettings - jclass clazz = env->FindClass("android/media/tv/tuner/FilterSettings$TsFilterSettings"); + jclass clazz = env->FindClass("android/media/tv/tuner/filter/TsFilterConfiguration"); int tpid = env->GetIntField(filterSettingsObj, env->GetFieldID(clazz, "mTpid", "I")); if (subtype == (int)DemuxTsFilterType::PES) { // DemuxFilterPesDataSettings jclass settingClazz = - env->FindClass("android/media/tv/tuner/FilterSettings$PesSettings"); + env->FindClass("android/media/tv/tuner/filter/PesSettings"); int streamId = env->GetIntField( settingsObj, env->GetFieldID(settingClazz, "mStreamId", "I")); bool isRaw = (bool)env->GetBooleanField( @@ -831,7 +831,7 @@ static int android_media_tv_Tuner_flush_filter(JNIEnv *env, jobject filter) { } static int android_media_tv_Tuner_read_filter_fmq( - JNIEnv *env, jobject filter, jbyteArray buffer, jint offset, jint size) { + JNIEnv *env, jobject filter, jbyteArray buffer, jlong offset, jlong size) { sp<Filter> filterSp = getFilter(env, filter); if (filterSp == NULL) { ALOGD("Failed to read filter FMQ: filter not found"); @@ -901,9 +901,14 @@ static int android_media_tv_Tuner_close_descrambler(JNIEnv, jobject) { return 0; } -static jobject android_media_tv_Tuner_open_dvr(JNIEnv *env, jobject thiz, jint type, jint bufferSize) { - sp<JTuner> tuner = getTuner(env, thiz); - return tuner->openDvr(static_cast<DvrType>(type), bufferSize); +static jobject android_media_tv_Tuner_open_dvr_recorder( + JNIEnv* /* env */, jobject /* thiz */, jlong /* bufferSize */) { + return NULL; +} + +static jobject android_media_tv_Tuner_open_dvr_playback( + JNIEnv* /* env */, jobject /* thiz */, jlong /* bufferSize */) { + return NULL; } static jobject android_media_tv_Tuner_get_demux_caps(JNIEnv*, jobject) { @@ -1019,35 +1024,35 @@ static void android_media_tv_Tuner_dvr_set_fd(JNIEnv *env, jobject dvr, jobject ALOGD("set fd = %d", dvrSp->mFd); } -static int android_media_tv_Tuner_read_dvr(JNIEnv *env, jobject dvr, jint size) { +static jlong android_media_tv_Tuner_read_dvr(JNIEnv *env, jobject dvr, jlong size) { sp<Dvr> dvrSp = getDvr(env, dvr); if (dvrSp == NULL) { ALOGD("Failed to read dvr: dvr not found"); } - int available = dvrSp->mDvrMQ->availableToWrite(); - int write = std::min(size, available); + long available = dvrSp->mDvrMQ->availableToWrite(); + long write = std::min((long) size, available); DvrMQ::MemTransaction tx; - int ret = 0; + long ret = 0; if (dvrSp->mDvrMQ->beginWrite(write, &tx)) { auto first = tx.getFirstRegion(); auto data = first.getAddress(); - int length = first.getLength(); - int firstToWrite = std::min(length, write); + long length = first.getLength(); + long firstToWrite = std::min(length, write); ret = read(dvrSp->mFd, data, firstToWrite); if (ret < firstToWrite) { - ALOGW("[DVR] file to MQ, first region: %d bytes to write, but %d bytes written", + ALOGW("[DVR] file to MQ, first region: %ld bytes to write, but %ld bytes written", firstToWrite, ret); } else if (firstToWrite < write) { - ALOGD("[DVR] write second region: %d bytes written, %d bytes in total", ret, write); + ALOGD("[DVR] write second region: %ld bytes written, %ld bytes in total", ret, write); auto second = tx.getSecondRegion(); data = second.getAddress(); length = second.getLength(); int secondToWrite = std::min(length, write - firstToWrite); ret += read(dvrSp->mFd, data, secondToWrite); } - ALOGD("[DVR] file to MQ: %d bytes need to be written, %d bytes written", write, ret); + ALOGD("[DVR] file to MQ: %ld bytes need to be written, %ld bytes written", write, ret); if (!dvrSp->mDvrMQ->commitWrite(ret)) { ALOGE("[DVR] Error: failed to commit write!"); } @@ -1055,17 +1060,17 @@ static int android_media_tv_Tuner_read_dvr(JNIEnv *env, jobject dvr, jint size) } else { ALOGE("dvrMq.beginWrite failed"); } - return ret; + return (jlong) ret; } -static int android_media_tv_Tuner_read_dvr_from_array( - JNIEnv /* *env */, jobject /* dvr */, jbyteArray /* bytes */, jint /* offset */, - jint /* size */) { +static jlong android_media_tv_Tuner_read_dvr_from_array( + JNIEnv /* *env */, jobject /* dvr */, jbyteArray /* bytes */, jlong /* offset */, + jlong /* size */) { //TODO: impl return 0; } -static int android_media_tv_Tuner_write_dvr(JNIEnv *env, jobject dvr, jint size) { +static jlong android_media_tv_Tuner_write_dvr(JNIEnv *env, jobject dvr, jlong size) { sp<Dvr> dvrSp = getDvr(env, dvr); if (dvrSp == NULL) { ALOGW("Failed to write dvr: dvr not found"); @@ -1079,28 +1084,28 @@ static int android_media_tv_Tuner_write_dvr(JNIEnv *env, jobject dvr, jint size) DvrMQ& dvrMq = dvrSp->getDvrMQ(); - int available = dvrMq.availableToRead(); - int toRead = std::min(size, available); + long available = dvrMq.availableToRead(); + long toRead = std::min((long) size, available); - int ret = 0; + long ret = 0; DvrMQ::MemTransaction tx; if (dvrMq.beginRead(toRead, &tx)) { auto first = tx.getFirstRegion(); auto data = first.getAddress(); - int length = first.getLength(); - int firstToRead = std::min(length, toRead); + long length = first.getLength(); + long firstToRead = std::min(length, toRead); ret = write(dvrSp->mFd, data, firstToRead); if (ret < firstToRead) { - ALOGW("[DVR] MQ to file: %d bytes read, but %d bytes written", firstToRead, ret); + ALOGW("[DVR] MQ to file: %ld bytes read, but %ld bytes written", firstToRead, ret); } else if (firstToRead < toRead) { - ALOGD("[DVR] read second region: %d bytes read, %d bytes in total", ret, toRead); + ALOGD("[DVR] read second region: %ld bytes read, %ld bytes in total", ret, toRead); auto second = tx.getSecondRegion(); data = second.getAddress(); length = second.getLength(); int secondToRead = toRead - firstToRead; ret += write(dvrSp->mFd, data, secondToRead); } - ALOGD("[DVR] MQ to file: %d bytes to be read, %d bytes written", toRead, ret); + ALOGD("[DVR] MQ to file: %ld bytes to be read, %ld bytes written", toRead, ret); if (!dvrMq.commitRead(ret)) { ALOGE("[DVR] Error: failed to commit read!"); } @@ -1109,12 +1114,12 @@ static int android_media_tv_Tuner_write_dvr(JNIEnv *env, jobject dvr, jint size) ALOGE("dvrMq.beginRead failed"); } - return ret; + return (jlong) ret; } -static int android_media_tv_Tuner_write_dvr_to_array( - JNIEnv /* *env */, jobject /* dvr */, jbyteArray /* bytes */, jint /* offset */, - jint /* size */) { +static jlong android_media_tv_Tuner_write_dvr_to_array( + JNIEnv /* *env */, jobject /* dvr */, jbyteArray /* bytes */, jlong /* offset */, + jlong /* size */) { //TODO: impl return 0; } @@ -1126,49 +1131,51 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_get_frontend_ids }, { "nativeOpenFrontendById", "(I)Landroid/media/tv/tuner/Tuner$Frontend;", (void *)android_media_tv_Tuner_open_frontend_by_id }, - { "nativeTune", "(ILandroid/media/tv/tuner/FrontendSettings;)I", + { "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I", (void *)android_media_tv_Tuner_tune }, { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune }, - { "nativeScan", "(ILandroid/media/tv/tuner/FrontendSettings;I)I", + { "nativeScan", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;I)I", (void *)android_media_tv_Tuner_scan }, { "nativeStopScan", "()I", (void *)android_media_tv_Tuner_stop_scan }, { "nativeSetLnb", "(I)I", (void *)android_media_tv_Tuner_set_lnb }, { "nativeSetLna", "(Z)I", (void *)android_media_tv_Tuner_set_lna }, - { "nativeGetFrontendStatus", "([I)[Landroid/media/tv/tuner/FrontendStatus;", + { "nativeGetFrontendStatus", "([I)Landroid/media/tv/tuner/frontend/FrontendStatus;", (void *)android_media_tv_Tuner_get_frontend_status }, - { "nativeGetAvSyncHwId", "(Landroid/media/tv/tuner/Tuner$Filter;)I", + { "nativeGetAvSyncHwId", "(Landroid/media/tv/tuner/Tuner/filter/Filter;)I", (void *)android_media_tv_Tuner_gat_av_sync_hw_id }, { "nativeGetAvSyncTime", "(I)J", (void *)android_media_tv_Tuner_gat_av_sync_time }, { "nativeConnectCiCam", "(I)I", (void *)android_media_tv_Tuner_connect_cicam }, { "nativeDisconnectCiCam", "()I", (void *)android_media_tv_Tuner_disconnect_cicam }, - { "nativeGetFrontendInfo", "(I)[Landroid/media/tv/tuner/FrontendInfo;", + { "nativeGetFrontendInfo", "(I)Landroid/media/tv/tuner/FrontendInfo;", (void *)android_media_tv_Tuner_get_frontend_info }, - { "nativeOpenFilter", "(III)Landroid/media/tv/tuner/Tuner$Filter;", + { "nativeOpenFilter", "(IIJ)Landroid/media/tv/tuner/Tuner/filter/Filter;", (void *)android_media_tv_Tuner_open_filter }, - { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/Tuner$TimeFilter;", + { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/Tuner/filter/TimeFilter;", (void *)android_media_tv_Tuner_open_time_filter }, { "nativeGetLnbIds", "()Ljava/util/List;", (void *)android_media_tv_Tuner_get_lnb_ids }, - { "nativeOpenLnbById", "(I)Landroid/media/tv/tuner/Tuner$Lnb;", + { "nativeOpenLnbById", "(I)Landroid/media/tv/tuner/Lnb;", (void *)android_media_tv_Tuner_open_lnb_by_id }, - { "nativeOpenDescrambler", "()Landroid/media/tv/tuner/Tuner$Descrambler;", + { "nativeOpenDescrambler", "()Landroid/media/tv/tuner/Descrambler;", (void *)android_media_tv_Tuner_open_descrambler }, - { "nativeOpenDvr", "(II)Landroid/media/tv/tuner/Tuner$Dvr;", - (void *)android_media_tv_Tuner_open_dvr }, + { "nativeOpenDvrRecorder", "(J)Landroid/media/tv/tuner/dvr/DvrRecorder;", + (void *)android_media_tv_Tuner_open_dvr_recorder }, + { "nativeOpenDvrPlayback", "(J)Landroid/media/tv/tuner/dvr/DvrPlayback;", + (void *)android_media_tv_Tuner_open_dvr_playback }, { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;", (void *)android_media_tv_Tuner_get_demux_caps }, }; static const JNINativeMethod gFilterMethods[] = { - { "nativeConfigureFilter", "(IILandroid/media/tv/tuner/FilterSettings;)I", + { "nativeConfigureFilter", "(IILandroid/media/tv/tuner/filter/FilterConfiguration;)I", (void *)android_media_tv_Tuner_configure_filter }, { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id }, - { "nativeSetDataSource", "(Landroid/media/tv/tuner/Tuner$Filter;)I", + { "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I", (void *)android_media_tv_Tuner_set_filter_data_source }, { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter }, { "nativeStopFilter", "()I", (void *)android_media_tv_Tuner_stop_filter }, { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter }, - { "nativeRead", "([BII)I", (void *)android_media_tv_Tuner_read_filter_fmq }, + { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq }, { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter }, }; @@ -1183,31 +1190,36 @@ static const JNINativeMethod gTimeFilterMethods[] = { }; static const JNINativeMethod gDescramblerMethods[] = { - { "nativeAddPid", "(IILandroid/media/tv/tuner/Tuner$Filter;)I", + { "nativeAddPid", "(IILandroid/media/tv/tuner/filter/Filter;)I", (void *)android_media_tv_Tuner_add_pid }, - { "nativeRemovePid", "(IILandroid/media/tv/tuner/Tuner$Filter;)I", + { "nativeRemovePid", "(IILandroid/media/tv/tuner/filter/Filter;)I", (void *)android_media_tv_Tuner_remove_pid }, { "nativeSetKeyToken", "([B)I", (void *)android_media_tv_Tuner_set_key_token }, { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_descrambler }, }; static const JNINativeMethod gDvrMethods[] = { - { "nativeAttachFilter", "(Landroid/media/tv/tuner/Tuner$Filter;)I", + { "nativeAttachFilter", "(Landroid/media/tv/tuner/filter/Filter;)I", (void *)android_media_tv_Tuner_attach_filter }, - { "nativeDetachFilter", "(Landroid/media/tv/tuner/Tuner$Filter;)I", + { "nativeDetachFilter", "(Landroid/media/tv/tuner/filter/Filter;)I", (void *)android_media_tv_Tuner_detach_filter }, - { "nativeConfigureDvr", "(Landroid/media/tv/tuner/DvrSettings;)I", + { "nativeConfigureDvr", "(Landroid/media/tv/tuner/dvr/DvrSettings;)I", (void *)android_media_tv_Tuner_configure_dvr }, { "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr }, { "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr }, { "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr }, { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_dvr }, - { "nativeSetFileDescriptor", "(Ljava/io/FileDescriptor;)V", - (void *)android_media_tv_Tuner_dvr_set_fd }, - { "nativeRead", "(I)I", (void *)android_media_tv_Tuner_read_dvr }, - { "nativeRead", "([BII)I", (void *)android_media_tv_Tuner_read_dvr_from_array }, - { "nativeWrite", "(I)I", (void *)android_media_tv_Tuner_write_dvr }, - { "nativeWrite", "([BII)I", (void *)android_media_tv_Tuner_write_dvr_to_array }, + { "nativeSetFileDescriptor", "(I)V", (void *)android_media_tv_Tuner_dvr_set_fd }, +}; + +static const JNINativeMethod gDvrRecorderMethods[] = { + { "nativeWrite", "(J)J", (void *)android_media_tv_Tuner_write_dvr }, + { "nativeWrite", "([BJJ)J", (void *)android_media_tv_Tuner_write_dvr_to_array }, +}; + +static const JNINativeMethod gDvrPlaybackMethods[] = { + { "nativeRead", "(J)J", (void *)android_media_tv_Tuner_read_dvr }, + { "nativeRead", "([BJJ)J", (void *)android_media_tv_Tuner_read_dvr_from_array }, }; static const JNINativeMethod gLnbMethods[] = { @@ -1225,35 +1237,49 @@ static bool register_android_media_tv_Tuner(JNIEnv *env) { return false; } if (AndroidRuntime::registerNativeMethods( - env, "android/media/tv/tuner/Tuner$Filter", + env, "android/media/tv/tuner/filter/Filter", gFilterMethods, NELEM(gFilterMethods)) != JNI_OK) { ALOGE("Failed to register filter native methods"); return false; } if (AndroidRuntime::registerNativeMethods( - env, "android/media/tv/tuner/Tuner$TimeFilter", + env, "android/media/tv/tuner/filter/TimeFilter", gTimeFilterMethods, NELEM(gTimeFilterMethods)) != JNI_OK) { ALOGE("Failed to register time filter native methods"); return false; } if (AndroidRuntime::registerNativeMethods( - env, "android/media/tv/tuner/Tuner$Descrambler", + env, "android/media/tv/tuner/Descrambler", gDescramblerMethods, NELEM(gDescramblerMethods)) != JNI_OK) { ALOGE("Failed to register descrambler native methods"); return false; } if (AndroidRuntime::registerNativeMethods( - env, "android/media/tv/tuner/Tuner$Dvr", + env, "android/media/tv/tuner/dvr/Dvr", gDvrMethods, NELEM(gDvrMethods)) != JNI_OK) { ALOGE("Failed to register dvr native methods"); return false; } if (AndroidRuntime::registerNativeMethods( - env, "android/media/tv/tuner/Tuner$Lnb", + env, "android/media/tv/tuner/dvr/DvrRecorder", + gDvrRecorderMethods, + NELEM(gDvrRecorderMethods)) != JNI_OK) { + ALOGE("Failed to register dvr recorder native methods"); + return false; + } + if (AndroidRuntime::registerNativeMethods( + env, "android/media/tv/tuner/dvr/DvrPlayback", + gDvrPlaybackMethods, + NELEM(gDvrPlaybackMethods)) != JNI_OK) { + ALOGE("Failed to register dvr playback native methods"); + return false; + } + if (AndroidRuntime::registerNativeMethods( + env, "android/media/tv/tuner/Lnb", gLnbMethods, NELEM(gLnbMethods)) != JNI_OK) { ALOGE("Failed to register lnb native methods"); diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index 484dbccca9a8..dfe879ffe9f3 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -4,6 +4,7 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <application android:label="@string/app_label"> diff --git a/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp b/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp index 4e49302debcd..01150b773557 100644 --- a/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp +++ b/packages/Incremental/NativeAdbDataLoader/jni/com_android_incremental_nativeadb_DataLoaderService.cpp @@ -32,6 +32,7 @@ #include <utils/Log.h> #include <charconv> +#include <span> #include <string> #include <thread> #include <type_traits> @@ -92,9 +93,7 @@ struct RequestCommand { static_assert(COMMAND_SIZE == sizeof(RequestCommand)); -static bool sendRequest(int fd, - RequestType requestType, - FileId fileId = -1, +static bool sendRequest(int fd, RequestType requestType, FileId fileId = -1, BlockIdx blockIdx = -1) { const RequestCommand command{ .requestType = static_cast<int16_t>(be16toh(requestType)), @@ -267,25 +266,24 @@ private: std::lock_guard lock{mMapsMutex}; CHECK(mIfs); for (auto&& pendingRead : pendingReads) { - const android::dataloader::Inode ino = pendingRead.file_ino; - const auto blockIdx = - static_cast<BlockIdx>(pendingRead.block_index); + const android::dataloader::FileId id = pendingRead.id; + const auto blockIdx = static_cast<BlockIdx>(pendingRead.block); /* ALOGI("[AdbDataLoader] Missing: %d", (int) blockIdx); */ - auto fileIdOr = getFileId(ino); + auto fileIdOr = getFileId(id); if (!fileIdOr) { - ALOGE("[AdbDataLoader] Failed to handle event for inode=%d. " + ALOGE("[AdbDataLoader] Failed to handle event for fileid=%s. " "Ignore.", - static_cast<int>(ino)); + android::incfs::toString(id).c_str()); continue; } const FileId fileId = *fileIdOr; if (mRequestedFiles.insert(fileId).second) { if (!sendRequest(mOutFd, PREFETCH, fileId, blockIdx)) { ALOGE("[AdbDataLoader] Failed to request prefetch for " - "inode=%d. Ignore.", - static_cast<int>(ino)); + "fileid=%s. Ignore.", + android::incfs::toString(id).c_str()); mRequestedFiles.erase(fileId); mStatusListener->reportStatus(DATA_LOADER_NO_CONNECTION); } @@ -296,7 +294,7 @@ private: struct TracedRead { uint64_t timestampUs; - uint64_t fileIno; + android::dataloader::FileId fileId; uint32_t firstBlockIdx; uint32_t count; }; @@ -307,26 +305,26 @@ private: return; } - TracedRead last = {0, 0, 0, 0}; + TracedRead last = {}; std::lock_guard lock{mMapsMutex}; for (auto&& read : pageReads) { - if (read.file_ino != last.fileIno || - read.block_index != last.firstBlockIdx + last.count) { + if (read.id != last.fileId || read.block != last.firstBlockIdx + last.count) { traceOrLogRead(last, trace, log); - last = {read.timestamp_us, read.file_ino, read.block_index, 1}; + last = {read.bootClockTsUs, read.id, (uint32_t)read.block, 1}; } else { ++last.count; } } traceOrLogRead(last, trace, log); } - void onFileCreated(android::dataloader::Inode inode, const android::dataloader::RawMetadata& metadata) { - } + void onFileCreated(android::dataloader::FileId fileid, + const android::dataloader::RawMetadata& metadata) {} private: void receiver() { std::vector<uint8_t> data; - std::vector<incfs_new_data_block> instructions; + std::vector<IncFsDataBlock> instructions; + std::unordered_map<android::dataloader::FileId, unique_fd> writeFds; while (!mStopReceiving) { const int res = waitForDataOrSignal(mInFd, mEventFd); if (res == 0) { @@ -366,21 +364,32 @@ private: mStopReceiving = true; break; } - const android::dataloader::Inode ino = mIdToNodeMap[header.fileId]; - if (!ino) { + const android::dataloader::FileId id = mIdToNodeMap[header.fileId]; + if (!android::incfs::isValidFileId(id)) { ALOGE("Unknown data destination for file ID %d. " "Ignore.", header.fileId); continue; } - auto inst = incfs_new_data_block{ - .file_ino = static_cast<__aligned_u64>(ino), - .block_index = static_cast<uint32_t>(header.blockIdx), - .data_len = static_cast<uint16_t>(header.blockSize), - .data = reinterpret_cast<uint64_t>( - remainingData.data()), - .compression = - static_cast<uint8_t>(header.compressionType)}; + + auto& writeFd = writeFds[id]; + if (writeFd < 0) { + writeFd.reset(this->mIfs->openWrite(id)); + if (writeFd < 0) { + ALOGE("Failed to open file %d for writing (%d). Aboring.", header.fileId, + -writeFd); + break; + } + } + + const auto inst = IncFsDataBlock{ + .fileFd = writeFd, + .pageIndex = static_cast<IncFsBlockIndex>(header.blockIdx), + .compression = static_cast<IncFsCompressionKind>(header.compressionType), + .kind = INCFS_BLOCK_KIND_DATA, + .dataSize = static_cast<uint16_t>(header.blockSize), + .data = (const char*)remainingData.data(), + }; instructions.push_back(inst); remainingData = remainingData.subspan(header.blockSize); } @@ -390,9 +399,8 @@ private: flushReadLog(); } - void writeInstructions(std::vector<incfs_new_data_block>& instructions) { - auto res = this->mIfs->writeBlocks(instructions.data(), - instructions.size()); + void writeInstructions(std::vector<IncFsDataBlock>& instructions) { + auto res = this->mIfs->writeBlocks(instructions); if (res != instructions.size()) { ALOGE("[AdbDataLoader] failed to write data to Incfs (res=%d when " "expecting %d)", @@ -406,30 +414,30 @@ private: FileId fileId; }; - MetaPair* updateMapsForFile(android::dataloader::Inode ino) { - android::dataloader::RawMetadata meta = mIfs->getRawMetadata(ino); + MetaPair* updateMapsForFile(android::dataloader::FileId id) { + android::dataloader::RawMetadata meta = mIfs->getRawMetadata(id); FileId fileId; auto res = std::from_chars(meta.data(), meta.data() + meta.size(), fileId); if (res.ec != std::errc{} || fileId < 0) { - ALOGE("[AdbDataLoader] Invalid metadata for inode=%d (%s)", - static_cast<int>(ino), meta.data()); + ALOGE("[AdbDataLoader] Invalid metadata for fileid=%s (%s)", + android::incfs::toString(id).c_str(), meta.data()); return nullptr; } - mIdToNodeMap[fileId] = ino; - auto& metaPair = mNodeToMetaMap[ino]; + mIdToNodeMap[fileId] = id; + auto& metaPair = mNodeToMetaMap[id]; metaPair.meta = std::move(meta); metaPair.fileId = fileId; return &metaPair; } - android::dataloader::RawMetadata* getMeta(android::dataloader::Inode ino) { - auto it = mNodeToMetaMap.find(ino); + android::dataloader::RawMetadata* getMeta(android::dataloader::FileId id) { + auto it = mNodeToMetaMap.find(id); if (it != mNodeToMetaMap.end()) { return &it->second.meta; } - auto metaPair = updateMapsForFile(ino); + auto metaPair = updateMapsForFile(id); if (!metaPair) { return nullptr; } @@ -437,13 +445,13 @@ private: return &metaPair->meta; } - FileId* getFileId(android::dataloader::Inode ino) { - auto it = mNodeToMetaMap.find(ino); + FileId* getFileId(android::dataloader::FileId id) { + auto it = mNodeToMetaMap.find(id); if (it != mNodeToMetaMap.end()) { return &it->second.fileId; } - auto* metaPair = updateMapsForFile(ino); + auto* metaPair = updateMapsForFile(id); if (!metaPair) { return nullptr; } @@ -456,7 +464,7 @@ private: return; } if (trace) { - auto* meta = getMeta(read.fileIno); + auto* meta = getMeta(read.fileId); auto str = android::base::StringPrintf( "page_read: index=%lld count=%lld meta=%.*s", static_cast<long long>(read.firstBlockIdx), @@ -468,7 +476,7 @@ private: if (log) { mReadLog.reserve(ReadLogBufferSize); - auto fileId = getFileId(read.fileIno); + auto fileId = getFileId(read.fileId); android::base::StringAppendF( &mReadLog, "%lld:%lld:%lld:%lld\n", static_cast<long long>(read.timestampUs), @@ -501,8 +509,8 @@ private: std::string mReadLog; std::thread mReceiverThread; std::mutex mMapsMutex; - std::unordered_map<android::dataloader::Inode, MetaPair> mNodeToMetaMap GUARDED_BY(mMapsMutex); - std::unordered_map<FileId, android::dataloader::Inode> mIdToNodeMap GUARDED_BY(mMapsMutex); + std::unordered_map<android::dataloader::FileId, MetaPair> mNodeToMetaMap GUARDED_BY(mMapsMutex); + std::unordered_map<FileId, android::dataloader::FileId> mIdToNodeMap GUARDED_BY(mMapsMutex); /** Tracks which files have been requested */ std::unordered_set<FileId> mRequestedFiles; std::atomic<bool> mStopReceiving = false; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index f6e5062039d2..1bec826897c0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1563,9 +1563,6 @@ class SettingsProtoDumpUtil { Settings.Global.WIFI_WAKEUP_ENABLED, GlobalSettingsProto.Wifi.WAKEUP_ENABLED); dumpSetting(s, p, - Settings.Global.WIFI_SAVED_STATE, - GlobalSettingsProto.Wifi.SAVED_STATE); - dumpSetting(s, p, Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS, GlobalSettingsProto.Wifi.SUPPLICANT_SCAN_INTERVAL_MS); dumpSetting(s, p, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 107207638205..874e29940202 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3415,7 +3415,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 186; + private static final int SETTINGS_VERSION = 187; private final int mUserId; @@ -4694,6 +4694,33 @@ public class SettingsProvider extends ContentProvider { currentVersion = 186; } + if (currentVersion == 186) { + // Remove unused wifi settings + getGlobalSettingsLocked().deleteSettingLocked( + "wifi_rtt_background_exec_gap_ms"); + getGlobalSettingsLocked().deleteSettingLocked( + "network_recommendation_request_timeout_ms"); + getGlobalSettingsLocked().deleteSettingLocked( + "wifi_suspend_optimizations_enabled"); + getGlobalSettingsLocked().deleteSettingLocked( + "wifi_is_unusable_event_metrics_enabled"); + getGlobalSettingsLocked().deleteSettingLocked( + "wifi_data_stall_min_tx_bad"); + getGlobalSettingsLocked().deleteSettingLocked( + "wifi_data_stall_min_tx_success_without_rx"); + getGlobalSettingsLocked().deleteSettingLocked( + "wifi_link_speed_metrics_enabled"); + getGlobalSettingsLocked().deleteSettingLocked( + "wifi_pno_frequency_culling_enabled"); + getGlobalSettingsLocked().deleteSettingLocked( + "wifi_pno_recency_sorting_enabled"); + getGlobalSettingsLocked().deleteSettingLocked( + "wifi_link_probing_enabled"); + getGlobalSettingsLocked().deleteSettingLocked( + "wifi_saved_state"); + currentVersion = 187; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 6d94a90d2c32..65269c9d5a89 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -543,7 +543,6 @@ public class SettingsBackupTest { Settings.Global.WIFI_ON, Settings.Global.WIFI_P2P_DEVICE_NAME, Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET, - Settings.Global.WIFI_SAVED_STATE, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS, Settings.Global.WIFI_SCAN_THROTTLE_ENABLED, diff --git a/packages/SharedStorageBackup/AndroidManifest.xml b/packages/SharedStorageBackup/AndroidManifest.xml index b8df88eb6133..c821766961f0 100644 --- a/packages/SharedStorageBackup/AndroidManifest.xml +++ b/packages/SharedStorageBackup/AndroidManifest.xml @@ -20,6 +20,7 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <application android:allowClearUserData="false" android:permission="android.permission.CONFIRM_FULL_BACKUP" diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index d40e7d4addab..0f3585303ed8 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -110,6 +110,7 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.CREATE_USERS" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6aeb0a159bea..26fa1cf46974 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -35,6 +35,7 @@ <!-- Used to read storage for all users --> <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.INJECT_EVENTS" /> diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 9f13a7b861a0..cff958faa7e1 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -16,5 +16,24 @@ } ] } + ], + "platinum-postsubmit": [ + { + "name": "PlatformScenarioTests", + "options": [ + { + "include-filter": "android.platform.test.scenario.sysui" + }, + { + "include-annotation": "android.platform.test.scenario.annotation.Scenario" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.helpers.Staging" + } + ] + } ] } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index 6518924ca0c2..17f2f476c9f2 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -133,6 +133,7 @@ public interface QSTile { public CharSequence label; public CharSequence secondaryLabel; public CharSequence contentDescription; + public CharSequence stateDescription; public CharSequence dualLabelContentDescription; public boolean disabledByPolicy; public boolean dualTarget = false; @@ -151,6 +152,7 @@ public interface QSTile { || !Objects.equals(other.label, label) || !Objects.equals(other.secondaryLabel, secondaryLabel) || !Objects.equals(other.contentDescription, contentDescription) + || !Objects.equals(other.stateDescription, stateDescription) || !Objects.equals(other.dualLabelContentDescription, dualLabelContentDescription) || !Objects.equals(other.expandedAccessibilityClassName, @@ -168,6 +170,7 @@ public interface QSTile { other.label = label; other.secondaryLabel = secondaryLabel; other.contentDescription = contentDescription; + other.stateDescription = stateDescription; other.dualLabelContentDescription = dualLabelContentDescription; other.expandedAccessibilityClassName = expandedAccessibilityClassName; other.disabledByPolicy = disabledByPolicy; @@ -195,6 +198,7 @@ public interface QSTile { sb.append(",label=").append(label); sb.append(",secondaryLabel=").append(secondaryLabel); sb.append(",contentDescription=").append(contentDescription); + sb.append(",stateDescription=").append(stateDescription); sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription); sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName); sb.append(",disabledByPolicy=").append(disabledByPolicy); diff --git a/packages/SystemUI/res-product/values-ky/strings.xml b/packages/SystemUI/res-product/values-ky/strings.xml index 8d96cb26bf30..043faeeaf89a 100644 --- a/packages/SystemUI/res-product/values-ky/strings.xml +++ b/packages/SystemUI/res-product/values-ky/strings.xml @@ -26,18 +26,18 @@ <string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"Планшетте SIM-карта жок."</string> <string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"Телефондо SIM-карта жок."</string> <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"PIN-коддор дал келген жок"</string> - <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"Планшеттин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул планшет баштапкы абалга келтирилип, андагы бардык маалымат жок кылынат."</string> - <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул телефон баштапкы абалга келтирилип, андагы бардык маалымат жок кылынат."</string> - <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Бул планшет баштапкы абалга келтирилип, андагы бардык маалымат жок кылынат."</string> - <string name="kg_failed_attempts_now_wiping" product="default" msgid="6381835450014881813">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес аракет жасадыңыз. Бул телефон баштапкы абалга келтирилип, андагы бардык маалымат жок кылынат."</string> - <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="7325071812832605911">"Планшеттин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы жок кылынат."</string> - <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="8110939900089863103">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы жок кылынат."</string> - <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="8509811676952707883">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы жок кылынат."</string> - <string name="kg_failed_attempts_now_erasing_user" product="default" msgid="3051962486994265014">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы жок кылынат."</string> - <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="1049523640263353830">"Планшетиңиздин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, жумуш профилиңиз өчүрүлүп, профилдеги бардык маалымат жок кылынат."</string> - <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, жумуш профилиңиз өчүрүлүп, профилдеги бардык маалымат жок кылынат."</string> - <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Жумуш профили өчүрүлүп, андагы бардык маалымат жок кылынат."</string> - <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Жумуш профили өчүрүлүп, андагы бардык маалымат жок кылынат."</string> + <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"Планшеттин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул планшет баштапкы абалга келтирилип, андагы бардык маалымат өчүрүлөт."</string> + <string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул телефон баштапкы абалга келтирилип, андагы бардык маалымат өчүрүлөт."</string> + <string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Бул планшет баштапкы абалга келтирилип, андагы бардык маалымат өчүрүлөт."</string> + <string name="kg_failed_attempts_now_wiping" product="default" msgid="6381835450014881813">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес аракет жасадыңыз. Бул телефон баштапкы абалга келтирилип, андагы бардык маалымат өчүрүлөт."</string> + <string name="kg_failed_attempts_almost_at_erase_user" product="tablet" msgid="7325071812832605911">"Планшеттин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы өчүрүлөт."</string> + <string name="kg_failed_attempts_almost_at_erase_user" product="default" msgid="8110939900089863103">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы өчүрүлөт."</string> + <string name="kg_failed_attempts_now_erasing_user" product="tablet" msgid="8509811676952707883">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы өчүрүлөт."</string> + <string name="kg_failed_attempts_now_erasing_user" product="default" msgid="3051962486994265014">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Бул колдонуучу өчүрүлүп, колдонуучунун бардык маалыматы өчүрүлөт."</string> + <string name="kg_failed_attempts_almost_at_erase_profile" product="tablet" msgid="1049523640263353830">"Планшетиңиздин кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, жумуш профилиңиз өчүрүлүп, профилдеги бардык маалымат өчүрүлөт."</string> + <string name="kg_failed_attempts_almost_at_erase_profile" product="default" msgid="3280816298678433681">"Телефондун кулпусун <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> жолу ийгиликсиз аракет кылсаңыз, жумуш профилиңиз өчүрүлүп, профилдеги бардык маалымат өчүрүлөт."</string> + <string name="kg_failed_attempts_now_erasing_profile" product="tablet" msgid="4417100487251371559">"Планшеттин кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Жумуш профили өчүрүлүп, андагы бардык маалымат өчүрүлөт."</string> + <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="4682221342671290678">"Телефондун кулпусун <xliff:g id="NUMBER">%d</xliff:g> жолу туура эмес ачууга аракет жасадыңыз. Жумуш профили өчүрүлүп, андагы бардык маалымат өчүрүлөт."</string> <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="1860049973474855672">"Графикалык ачкычты <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес тарттыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> ийгиликсиз аракеттен кийин планшетиңизди бөгөттөн электрондук почтаңыз аркылуу чыгаруу талап кылынат.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> секунддан кийин кайра аракеттениңиз."</string> <string name="kg_failed_attempts_almost_at_login" product="default" msgid="44112553371516141">"Графикалык ачкычты <xliff:g id="NUMBER_0">%1$d</xliff:g> жолу туура эмес тарттыңыз. Дагы <xliff:g id="NUMBER_1">%2$d</xliff:g> ийгиликсиз аракеттен кийин телефонуңузду бөгөттөн электрондук почтаңыз аркылуу чыгаруу талап кылынат.\n\n <xliff:g id="NUMBER_2">%3$d</xliff:g> секунддан кийин кайра аракеттениңиз."</string> </resources> diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 4de854314b1a..823685e15807 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoem om skerm te vul"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Strek om skerm te vul"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Skermkiekie"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Prent is ingevoeg"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Stoor tans skermkiekie..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Stoor tans skermkiekie..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skermkiekie is gestoor"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Begin opname"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Neem stemopname op"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Wys tikke"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tik om te stop"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stop"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Laat wag"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Hervat"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Batterybespaarder"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Aan met sonsondergang"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Tot sonsopkoms"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aan om <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Tot <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is gedeaktiveer"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is geaktiveer"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Skermopname"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Begin"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stop"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Swiep op om programme te wissel"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Sleep regs om programme vinnig te wissel"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Wissel oorsig"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Pasmaak"</string> <string name="notification_done" msgid="6215117625922713976">"Klaar"</string> <string name="inline_undo" msgid="9026953267645116526">"Ontdoen"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Merk hierdie kennisgewing as \"nie \'n gesprek nie\""</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Gunsteling"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Ontmerk as gunsteling"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Demp"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Ontdemp"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Wys as borrel"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Skakel borrels af"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Voeg by tuisskerm"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"kennisgewingkontroles"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"kennisgewing-sluimeropsies"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 87603ac2051a..c3248249ebb6 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"ማያ እንዲሞላ አጉላ"</string> <string name="compat_mode_off" msgid="7682459748279487945">"ማያ ለመሙለት ሳብ"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"ቅጽበታዊ ገጽ እይታ"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"ምስል ገብቷል"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"ቅጽበታዊ ገጽ እይታ በማስቀመጥ ላይ..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ቅጽበታዊ ገጽ እይታ በማስቀመጥ ላይ..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ቅጽበታዊ ገጽ እይታ ተቀምጧል"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"መቅረጽ ጀምር"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"ድምጽን ቅረጽ"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"መታ ማድረጎችን አሳይ"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"ለማቆም መታ ያድርጉ"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"አቁም"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"ባለበት አቁም"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"ከቆመበት ቀጥል"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ባትሪ ቆጣቢ"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ጸሐይ ስትጠልቅ ይበራል"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ጸሐይ እስክትወጣ ድረስ"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> ላይ ይበራል"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"እስከ <xliff:g id="TIME">%s</xliff:g> ድረስ"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"ኤንኤፍሲ"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"ኤንኤፍሲ ተሰናክሏል"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"ኤንኤፍሲ ነቅቷል"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index c764004fddfe..113d705d7f31 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"تكبير/تصغير لملء الشاشة"</string> <string name="compat_mode_off" msgid="7682459748279487945">"توسيع بملء الشاشة"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"لقطة شاشة"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"تم إدراج الصورة"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"جارٍ حفظ لقطة الشاشة..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"جارٍ حفظ لقطة الشاشة..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"تم حفظ لقطة الشاشة."</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"بدء التسجيل"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"تسجيل التعليق الصوتي"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"عرض النقرات"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"انقر لإيقاف التسجيل"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"إيقاف"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"إيقاف مؤقت"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"استئناف"</string> @@ -396,15 +398,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"توفير شحن البطارية"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"تفعيل عند غروب الشمس"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"حتى شروق الشمس"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"تفعيل الوضع في <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"حتى <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"الاتصالات قصيرة المدى (NFC)"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"تم إيقاف الاتصال القريب المدى"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"تم تفعيل الاتصال القريب المدى"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"تسجيل الشاشة"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"بدء"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"إيقاف"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"مرّر سريعًا لأعلى لتبديل التطبيقات"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"اسحب لليسار للتبديل السريع بين التطبيقات"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"تبديل \"النظرة العامة\""</string> @@ -708,22 +709,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"تخصيص"</string> <string name="notification_done" msgid="6215117625922713976">"تم"</string> <string name="inline_undo" msgid="9026953267645116526">"تراجع"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"تحويل الإشعار من محادثة إلى إشعار عادي"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"المفضّلة"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"إزالة المحادثة من المفضّلة"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"كتم الصوت"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"إعادة الصوت"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"إظهار الإشعار كفقاعة تفسيرية"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"إيقاف الفقاعات التفسيرية"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"إضافة إلى الشاشة الرئيسية"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"عناصر التحكم في الإشعارات"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"خيارات تأجيل الإشعارات"</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 2fc393c6ec68..975d639c2ea4 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"স্ক্ৰীণ পূর্ণ কৰিবলৈ জুম কৰক"</string> <string name="compat_mode_off" msgid="7682459748279487945">"স্ক্ৰীণ পূর্ণ কৰিবলৈ প্ৰসাৰিত কৰক"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"স্ক্ৰীনশ্বট"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"প্ৰতিচ্ছবি ভৰোৱা হ’ল"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"স্ক্ৰীণশ্বট ছেভ কৰি থকা হৈছে…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"স্ক্ৰীণশ্বট ছেভ কৰি থকা হৈছে…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্ৰীণশ্বট ছেভ কৰা হ’ল"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"ৰেকৰ্ডিং কৰা আৰম্ভ কৰক"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"পাৰ্শ্ব-ধ্বনি ৰেকৰ্ড কৰক"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"টিপা ঠাইসমূহ দেখুৱাওক"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"বন্ধ কৰিবলৈ টিপক"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"বন্ধ কৰক"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"প\'জ কৰক"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"ৰখোৱাৰ পৰা পুনৰ আৰম্ভ কৰক"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"বেটাৰী সঞ্চয়কাৰী"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"সূৰ্যাস্তত অন হয়"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"সূৰ্যোদয়লৈকে"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>ত অন কৰক"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> পৰ্যন্ত"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC নিষ্ক্ৰিয় হৈ আছে"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC সক্ষম হৈ আছে"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"স্ক্ৰীন ৰেকর্ড"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"আৰম্ভ কৰক"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"বন্ধ কৰক"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"আনটো এপ্ ব্য়ৱহাৰ কৰিবলৈ ওপৰলৈ ছোৱাইপ কৰক"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"খৰতকীয়াকৈ আনটো এপ্ ব্য়ৱহাৰ কৰিবলৈ সোঁফালে টানক"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"অৱলোকন ট’গল কৰক"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"নিজৰ উপযোগিতা অনুসৰি"</string> <string name="notification_done" msgid="6215117625922713976">"সম্পন্ন হ’ল"</string> <string name="inline_undo" msgid="9026953267645116526">"আনডু কৰক"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"এই জাননীখন এটা বার্তালাপ নহয় বুলি চিহ্নিত কৰক"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"অপ্ৰিয় হিচাপে চিহ্নিত কৰক"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"অপ্ৰিয় হিচাপে চিহ্নিত কৰক"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"মিউট কৰক"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"আনমিউট কৰক"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"বাবল হিচাপে দেখুৱাওক"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"বাবলসমূহ অফ কৰক"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"গৃহ স্ক্ৰীনত যোগ কৰক"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"জাননীৰ নিয়ন্ত্ৰণসমূহ"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"জাননীক স্নুজ কৰাৰ বিকল্পসমূহ"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index de9c60520d84..d98d939da47a 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Ekranı doldurmaq üçün yaxınlaşdır"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Ekranı doldurmaq üçün uzat"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Ekran şəkli"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Şəkil daxil edildi"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Skrinşot yadda saxlanılır..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Skrinşot yadda saxlanır..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinşot yadda saxlandı"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Ekranın Video Çəkimini Başladın"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Ekranın səsli video çəkimi"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Klikləmələri göstərin"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Dayandırmaq üçün toxunun"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Dayandırın"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Dayandırın"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Davam edin"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Enerjiyə qənaət"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Qürubda aktiv olacaq"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Şəfəq vaxtına qədər"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Bu vaxt aktiv olur: <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Bu vaxtadək: <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC deaktiv edilib"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC aktiv edilib"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekran yazması"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Başlayın"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Dayandırın"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Tətbiqi dəyişmək üçün yuxarı sürüşdürün"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Tətbiqləri cəld dəyişmək üçün sağa çəkin"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"İcmala Keçin"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Fərdiləşdirin"</string> <string name="notification_done" msgid="6215117625922713976">"Hazırdır"</string> <string name="inline_undo" msgid="9026953267645116526">"Ləğv edin"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Bu bildirişi \"söhbət deyil\" kimi qeyd edin."</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Sevimli"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Sevimlilərdən silin"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Səssiz"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Susdurmayın"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Qabarcıq kimi göstərin"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Qabarcıqları deaktiv edin"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Əsas ekrana əlavə edin"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"bildiriş nəzarəti"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"bildiriş təxirə salma seçimləri"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index a895f35cca72..ff03bb9726ae 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zumiraj na celom ekranu"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Razvuci na ceo ekran"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Snimak ekrana"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Slika je umetnuta"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Čuvanje snimka ekrana..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Čuvanje snimka ekrana..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Započni snimanje"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Snimi prenos glasa"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Prikazuj dodire"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Dodirnite da biste zaustavili"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Zaustavi"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pauziraj"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Nastavi"</string> @@ -390,6 +392,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Ušteda baterije"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Uključuje se po zalasku sunca"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do izlaska sunca"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Uključuje se u <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je onemogućen"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je omogućen"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 67aa8996c4ff..1ebbb5ea100c 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Павял. на ўвесь экран"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Расцягн. на ўвесь экран"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Здымак экрана"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Відарыс устаўлены"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Захаванне скрыншота..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Захаванне скрыншота..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Здымак экрана захаваны"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Пачаць запіс"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Закадравае агучванне запісу"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Паказваць дотыкі"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Націсніце, каб спыніць"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Спыніць"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Прыпыніць"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Узнавіць"</string> @@ -394,15 +396,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Эканомія зараду"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Уключана ўвечары"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Да ўсходу сонца"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Уключана ў <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Да <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC адключаны"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC уключаны"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Запіс экрана"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Пачаць"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Спыніць"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Правядзіце ўверх, каб пераключыць праграмы"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Каб хутка пераключыцца паміж праграмамі, перацягніце ўправа"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Уключыць/выключыць агляд"</string> @@ -704,22 +705,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Наладзіць"</string> <string name="notification_done" msgid="6215117625922713976">"Гатова"</string> <string name="inline_undo" msgid="9026953267645116526">"Адрабіць"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Не пазначаць гэта апавяшчэнне як размову"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"У абранае"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Выдаліць з абранага"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Ігнараваць"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Уключыць паказ"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Паказваць як усплывальнае апавяшчэнне"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Выключыць усплывальныя апавяшчэнні"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Дадаць на галоўны экран"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"кіраванне апавяшчэннямі"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"параметры адкладвання апавяшчэнняў"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index f83bf96e2b0d..5cfea60f901f 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Мащаб – запълва екрана"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Разпъване – запълва екрана"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Екранна снимка"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Изображението бе вмъкнато"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Екранната снимка се запазва..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Екранната снимка се запазва..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Екранната снимка е запазена"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Стартиране на записа"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Записване на озвучаване"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Показване на докосванията"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Докоснете за спиране"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Спиране"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Поставяне на пауза"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Възобновяване"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Запазв. на батерията"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Ще се вкл. по залез"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"До изгрев"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ще се включи в <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"До <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"КБП"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"КБП е деактивирана"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"КБП е активирана"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 10479b134c89..f1e49ce57ce8 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"স্ক্রীণ পূরণ করতে জুম করুন"</string> <string name="compat_mode_off" msgid="7682459748279487945">"ফুল স্ক্রিন করুন"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"স্ক্রিনশট"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"ছবি যোগ করা হয়েছে"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"স্ক্রিনশট সেভ করা হচ্ছে..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"স্ক্রিনশট সেভ করা হচ্ছে..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্রিনশট সেভ করা হয়েছে"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"রেকর্ডিং শুরু করুন"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"ভয়েসওভার রেকর্ড করুন"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"ট্যাপগুলি দেখুন"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"বন্ধ করতে ট্যাপ করুন"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"বন্ধ করুন"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"পজ করুন"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"আবার চালু করুন"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ব্যাটারি সেভার"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"সূর্যাস্তে চালু হবে"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"সূর্যোদয় পর্যন্ত"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>-এ চালু হবে"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> পর্যন্ত"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC অক্ষম করা আছে"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC সক্ষম করা আছে"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"স্ক্রিন রেকর্ড"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"শুরু করুন"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"বন্ধ করুন"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"অন্য অ্যাপে যেতে উপরের দিকে সোয়াইপ করুন"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"একটি অ্যাপ ছেড়ে দ্রুত অন্য অ্যাপে যেতে ডান দিকে টেনে আনুন"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"\'এক নজরে\' বৈশিষ্ট্যটি চালু বা বন্ধ করুন"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"কাস্টমাইজ করুন"</string> <string name="notification_done" msgid="6215117625922713976">"সম্পন্ন"</string> <string name="inline_undo" msgid="9026953267645116526">"আগের অবস্থায় ফিরে যান"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"কথোপকথন হিসেবে এই বিজ্ঞপ্তি চিহ্নিত করবেন না"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"পছন্দসই"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"পছন্দসই থেকে সরান"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"মিউট করুন"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"আনমিউট করুন"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"পপ-আপ হিসেবে দেখুন"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"পপ-আপ বন্ধ করুন"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"হোম স্ক্রিনে যোগ করুন"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"বিজ্ঞপ্তির নিয়ন্ত্রণগুলি"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"বিজ্ঞপ্তি মনে করিয়ে দেওয়ার বিকল্পগুলি"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 6c89d976731a..cabe87f4a0b0 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Uvećaj prikaz na ekran"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Razvuci prikaz na ekran"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Snimak ekrana"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Slika je umetnuta"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Spašavanje snimka ekrana..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Spašavanje snimka ekrana..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Započni snimanje"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Govor snimka"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Prikaži dodire"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Dodirnite za zaustavljanje"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Zaustavi"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pauza"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Nastavi"</string> @@ -390,6 +392,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Ušteda baterije"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Uključuje se u sumrak"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do svitanja"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Uključuje se u <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je onemogućen"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je omogućen"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index f675e20e7dfd..3e257952050a 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom per omplir pantalla"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Estira per omplir pant."</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Captura de pantalla"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Imatge inserida"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"S\'està desant captura de pantalla..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"S\'està desant la captura de pantalla..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"S\'ha desat la captura de pantalla"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Inicia la gravació"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Grava la veu en off"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Mostra els tocs"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Toca per aturar"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Atura"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Posa en pausa"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Reprèn"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Estalvi de bateria"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Al vespre"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Fins a l\'alba"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"S\'activarà a les <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Fins a les <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"L\'NFC està desactivada"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"L\'NFC està activada"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 354ccf44af39..3c85880eaaf8 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Přiblížit na celou obrazovku"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Na celou obrazovku"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Snímek obrazovky"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Vložen obrázek"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Ukládání snímku obrazovky..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Ukládání snímku obrazovky..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Snímek obrazovky byl uložen"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Spustit nahrávání"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Nahrávat komentář"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Zobrazovat klepnutí"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Klepnutím zastavíte"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Zastavit"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pozastavit"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Obnovit"</string> @@ -392,6 +394,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Spořič baterie"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Při soumraku"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do svítání"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Zapnout v <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je vypnuto"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je zapnuto"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 04e83d4b62ea..f2ffdaac0e37 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom til fuld skærm"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Stræk til fuld skærm"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Billedet blev indsat"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Gemmer screenshot..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Gemmer screenshot..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshottet blev gemt"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Start optagelse"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Optag voiceover"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Vis tryk"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tryk for at stoppe"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stop"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Sæt på pause"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Genoptag"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Batterisparefunktion"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Til ved solnedgang"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Indtil solopgang"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Tænd kl. <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Indtil kl. <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC er deaktiveret"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC er aktiveret"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index baf3e7c0006e..58caef2b46bb 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom auf Bildschirmgröße"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Auf Bildschirmgröße anpassen"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Bild eingefügt"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Screenshot wird gespeichert..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Screenshot wird gespeichert..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot gespeichert"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Aufzeichnung starten"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Voice-over aufnehmen"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Fingertipps anzeigen"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Zum Stoppen tippen"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Anhalten"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pausieren"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Fortsetzen"</string> @@ -392,15 +394,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Energiesparmodus"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"An bei Sonnenuntergang"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Bis Sonnenaufgang"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"An um <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Bis <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ist deaktiviert"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ist aktiviert"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Bildschirmaufnahme"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Starten"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Beenden"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Nach oben wischen, um Apps zu wechseln"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Zum schnellen Wechseln der Apps nach rechts ziehen"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Übersicht ein-/ausblenden"</string> @@ -700,22 +701,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Anpassen"</string> <string name="notification_done" msgid="6215117625922713976">"Fertig"</string> <string name="inline_undo" msgid="9026953267645116526">"Rückgängig machen"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Diese Benachrichtigung als keine Unterhaltung markieren"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favorit"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Aus Favoriten entfernen"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Stummschalten"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Stummschaltung aufheben"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Als Infofeld anzeigen"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Infofelder deaktivieren"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Zum Startbildschirm hinzufügen"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g> – <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"Benachrichtigungseinstellungen"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"Optionen für spätere Erinnerung bei Benachrichtigungen"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 45ad30244812..d3686c834882 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Ζουμ σε πλήρη οθόνη"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Προβoλή σε πλήρη οθ."</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Στιγμιότυπο οθόνης"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Έγινε εισαγωγή εικόνας"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Αποθήκ. στιγμιότυπου οθόνης..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Αποθήκευση στιγμιότυπου οθόνης..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Το στιγμιότυπο οθόνης αποθηκεύτηκε"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Έναρξη εγγραφής"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Εγγραφή σπικάζ"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Εμφάνιση πατημάτων"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Πατήστε για διακοπή"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Διακοπή"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Παύση"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Συνέχιση"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Εξοικονόμ. μπαταρίας"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Κατά τη δύση"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Μέχρι την ανατολή"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ενεργοποίηση στις <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Έως <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Το NFC είναι απενεργοποιημένο"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Το NFC είναι ενεργοποιημένο"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index b6a5d1573acc..4f61daa2bf18 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom to fill screen"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Stretch to fill screen"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Image inserted"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Saving screenshot…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Saving screenshot…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Start Recording"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Record voiceover"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Show taps"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tap to stop"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stop"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pause"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Resume"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Battery Saver"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"On at sunset"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Until sunrise"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"On at <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Until <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index e194e69c9299..34a2f1975d75 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom to fill screen"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Stretch to fill screen"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Image inserted"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Saving screenshot…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Saving screenshot…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Start Recording"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Record voiceover"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Show taps"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tap to stop"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stop"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pause"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Resume"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Battery Saver"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"On at sunset"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Until sunrise"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"On at <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Until <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index b6a5d1573acc..4f61daa2bf18 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom to fill screen"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Stretch to fill screen"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Image inserted"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Saving screenshot…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Saving screenshot…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Start Recording"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Record voiceover"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Show taps"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tap to stop"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stop"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pause"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Resume"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Battery Saver"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"On at sunset"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Until sunrise"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"On at <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Until <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index b6a5d1573acc..4f61daa2bf18 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom to fill screen"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Stretch to fill screen"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Image inserted"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Saving screenshot…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Saving screenshot…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Start Recording"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Record voiceover"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Show taps"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tap to stop"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stop"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pause"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Resume"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Battery Saver"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"On at sunset"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Until sunrise"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"On at <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Until <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index e3a569a29cae..2bb51311aa0a 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom to fill screen"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Stretch to fill screen"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Image inserted"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Saving screenshot…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Saving screenshot…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Start Recording"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Record voiceover"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Show taps"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tap to stop"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stop"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pause"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Resume"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Battery Saver"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"On at sunset"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Until sunrise"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"On at <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Until <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 255db9229657..e2bf6efa538e 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom para ocupar la pantalla"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Estirar p/ ocupar la pantalla"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Captura de pantalla"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Se insertó la imagen"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Guardando captura de pantalla"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Guardando la captura de pantalla..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Se guardó la captura de pantalla"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Iniciar grabación"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Grabar voz superpuesta"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Mostrar toques"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Presiona para detener"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Detener"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pausar"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Reanudar"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Ahorro de batería"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Al atardecer"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hasta el amanecer"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"A la(s) <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Hasta la(s) <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"La tecnología NFC está inhabilitada"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"La tecnología NFC está habilitada"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index d1d959c84099..40d7fe3bdf25 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom para ajustar"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Expandir para ajustar"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Captura de pantalla"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Se ha insertado la imagen"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Guardando captura..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Guardando captura..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Se ha guardado la captura de pantalla"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Iniciar grabación"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Grabar voz en off"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Mostrar toques"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Toca para detener"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Detener"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pausar"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Seguir"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Ahorro de batería"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Al anochecer"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hasta el amanecer"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"A las <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Hasta las <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"El NFC está desactivado"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"El NFC está activado"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 6f69a17f35f0..d8f546ce48d4 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Suumi ekraani täitmiseks"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Venita ekraani täitmiseks"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Ekraanipilt"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Pilt on sisestatud"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Kuvatõmmise salvestamine ..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Kuvatõmmise salvestamine ..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekraanipilt salvestati"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Alusta salvestamist"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Salvesta hääl"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Kuva puudutused"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Puudutage peatamiseks"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Peata"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Peata"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Jätka"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Akusäästja"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Sisse päikeselooj."</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Kuni päikesetõusuni"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Sisse kell <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Kuni <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC on keelatud"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC on lubatud"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekraanikirje"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Alustage"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Peatage"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Rakenduste vahetamiseks pühkige üles"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Lohistage paremale, et rakendusi kiiresti vahetada"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Lehe Ülevaade sisse- ja väljalülitamine"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Kohandamine"</string> <string name="notification_done" msgid="6215117625922713976">"Valmis"</string> <string name="inline_undo" msgid="9026953267645116526">"Võta tagasi"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Eemalda see meeldetuletus vestlustest"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Lisa lemmikutesse"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Tühista lemmikutesse lisamine"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Vaigista"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Tühista vaigistus"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Kuva mullina"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Lülita mullid välja"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Lisa avakuvale"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"märguannete juhtnupud"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"märguannete edasilükkamise valikud"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 1b89c5aa92d2..9dceb5fe3f05 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Handiagotu pantaila betetzeko"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Luzatu pantaila betetzeko"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Pantaila-argazkia"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Irudi bat txertatu da"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Pantaila-argazkia gordetzen…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Pantaila-argazkia gordetzen…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Gorde da pantaila-argazkia"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Hasi grabatzen"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Grabatu off ahotsa"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Erakutsi sakatzeak"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Sakatu gelditzeko"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Gelditu"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pausatu"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Berrekin"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Bateria-aurrezlea"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Ilunabarrean aktibatuko da"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Egunsentira arte"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aktibatze-ordua: <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Desaktibatze-ordua: <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Desgaituta dago NFC"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Gaituta dago NFC"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Pantaila-grabaketa"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Hasi"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Gelditu"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Egin gora aplikazioa aldatzeko"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Arrastatu eskuinera aplikazioa azkar aldatzeko"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Aldatu ikuspegi orokorra"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Pertsonalizatu"</string> <string name="notification_done" msgid="6215117625922713976">"Eginda"</string> <string name="inline_undo" msgid="9026953267645116526">"Desegin"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Markatu jakinarazpen hau ez dela elkarrizketa bat"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Markatu gogoko gisa"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Kendu gogokoetatik"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Ezkutatu"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Erakutsi"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Markatu burbuila gisa"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Desaktibatu burbuilak"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Gehitu hasierako pantailan"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"jakinarazpena kontrolatzeko aukerak"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"jakinarazpena atzeratzeko aukerak"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 2b765573ac06..d27b420c0e9c 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"بزرگنمایی برای پر کردن صفحه"</string> <string name="compat_mode_off" msgid="7682459748279487945">"گسترده کردن برای پر کردن صفحه"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"عکس صفحهنمایش"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"تصویر درج شد"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"در حال ذخیره عکس صفحهنمایش..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"درحال ذخیره عکس صفحهنمایش…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"عکس صفحهنمایش ذخیره شد"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"شروع ضبط"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"ضبط صدا روی تصویر"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"نمایش ضربهها"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"ضربه برای توقف"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"توقف"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"مکث"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"ازسرگیری"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"بهینهسازی باتری"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"غروب روشن میشود"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"تا طلوع آفتاب"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"ساعت <xliff:g id="TIME">%s</xliff:g> روشن میشود"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"تا<xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC غیرفعال است"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC فعال است"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ضبط کردن صفحهنمایش"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"شروع"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"توقف"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"برای تغییر برنامهها، تند به بالا بکشید"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"برای جابهجایی سریع میان برنامهها، به چپ بکشید"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"تغییر وضعیت نمای کلی"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"سفارشی کردن"</string> <string name="notification_done" msgid="6215117625922713976">"تمام"</string> <string name="inline_undo" msgid="9026953267645116526">"واگرد"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"علامتگذاری این اعلان بهعنوان غیرمکالمه"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"مورد دلخواه"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"حذف از موارد دلخواه"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"صامت کردن"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"باصدا کردن"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"نمایش بهشکل ابزارک اعلان"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"خاموش کردن ابزارکهای اعلان"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"افزودن به صفحه اصلی"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"کنترلهای اعلان"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"گزینههای تعویق اعلان"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 9b76f39175ab..cf01efd933d0 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoomaa koko näyttöön"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Venytä koko näyttöön"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Kuvakaappaus"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Kuva lisätty"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Tallennetaan kuvakaappausta..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Tallennetaan kuvakaappausta..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Kuvakaappaus tallennettu"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Aloita tallennus"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Äänitä taustaselostus"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Näytä napautukset"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Lopeta napauttamalla"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Lopeta"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Keskeytä"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Jatka"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Virransäästö"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Auringon laskiessa"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Auringonnousuun"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Päälle klo <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> asti"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC on poistettu käytöstä"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC on käytössä"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Näytön tallentaminen"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Aloita"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Lopeta"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Vaihda sovellusta pyyhkäisemällä ylös"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Vaihda sovellusta nopeasti vetämällä oikealle"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Näytä/piilota viimeisimmät"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Muokkaa"</string> <string name="notification_done" msgid="6215117625922713976">"Valmis"</string> <string name="inline_undo" msgid="9026953267645116526">"Kumoa"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Merkitse, että tämä ilmoitus ei ole keskustelu"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Suosikki"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Poista suosikeista"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Ohita"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Poista ohitus"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Näytä ohjekuplana"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Laita ohjekuplat pois päältä"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Lisää aloitusnäytölle"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"Ilmoitusten hallinta"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"Ilmoitusten torkkuasetukset"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 55eb39ee69f2..5f7a358239c7 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoomer pour remplir l\'écran"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Étirer pour remplir l\'écran"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Capture d\'écran"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Image insérée"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Enregistrement capture écran…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Enregistrement capture écran…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Commencer l\'enregistrement"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Enregistrer la voix hors champ"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Afficher les éléments sélectionnés"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Toucher pour arrêter"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Arrêter"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pause"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Reprendre"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Économiseur de pile"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Activé la nuit"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Jusqu\'à l\'aube"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Actif à <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Jusqu\'à <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC désactivée"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC activée"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 5b126129311e..3c12f668d2b9 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoomer pour remplir l\'écran"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Étirer pour remplir l\'écran"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Capture d\'écran"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Image insérée"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Enregistrement capture écran…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Enregistrement de la capture d\'écran…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Démarrer l\'enregistrement"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Enregistrer une voix off"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Afficher les éléments sélectionnés"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Appuyez ici pour arrêter"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Arrêter"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pause"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Reprendre"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Économiseur batterie"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Activé la nuit"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Jusqu\'à l\'aube"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"À partir de <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Jusqu\'à <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC désactivée"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"La technologie NFC est activée"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 74ee202f9926..0c444e48aee3 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Ampliar ata ocupar todo"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Estirar ata ocupar todo"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Crear captura"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Engadiuse a imaxe"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Gardando captura de pantalla…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Gardando captura de pantalla…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Gardouse a captura de pantalla"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Iniciar gravación"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Gravar voz en off"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Mostrar toques"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Toca para deter a gravación"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Deter"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pór en pausa"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Retomar"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Aforro de batería"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Activación ao solpor"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Ata o amencer"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Activarase ás: <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Utilizarase ata as: <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"A opción NFC está desactivada"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"A opción NFC está activada"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 4b5dd2519055..9dde523d9a89 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"સ્ક્રીન ભરવા માટે ઝૂમ કરો"</string> <string name="compat_mode_off" msgid="7682459748279487945">"સ્ક્રીન ભરવા માટે ખેંચો"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"સ્ક્રીનશૉટ"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"છબી શામેલ કરી"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"સ્ક્રીનશોટ સાચવી રહ્યું છે…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"સ્ક્રીનશોટ સાચવી રહ્યું છે…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"સ્ક્રીનશૉટ સાચવ્યો"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"રેકોર્ડિંગ શરૂ કરો"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"વૉઇસઓવર રેકોર્ડ કરો"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"ટૅપ કર્યાની સંખ્યા બતાવો"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"રોકવા માટે ટૅપ કરો"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"રોકો"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"થોભાવો"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"ફરી શરૂ કરો"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"બૅટરી સેવર"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"સૂર્યાસ્ત વખતે"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"સૂર્યોદય સુધી"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> વાગ્યે ચાલુ"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> વાગ્યા સુધી"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC અક્ષમ કરેલ છે"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC સક્ષમ કરેલ છે"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"સ્ક્રીન રેકૉર્ડ કરો"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"શરૂ કરો"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"રોકો"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ઍપ સ્વિચ કરવા માટે ઉપરની તરફ સ્વાઇપ કરો"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ઍપને ઝડપથી સ્વિચ કરવા માટે જમણે ખેંચો"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ઝલકને ટૉગલ કરો"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"કસ્ટમાઇઝ કરો"</string> <string name="notification_done" msgid="6215117625922713976">"થઈ ગયું"</string> <string name="inline_undo" msgid="9026953267645116526">"રદ કરો"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"આ નોટિફિકેશન વાતચીત ન હોવા તરીકે માર્ક કરો"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"મનપસંદ"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"મનપસંદમાંથી કાઢી નાખો"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"મ્યૂટ કરો"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"અનમ્યૂટ કરો"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"બબલ તરીકે બતાવો"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"બબલ બંધ કરો"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"હોમ સ્ક્રીન પર ઉમેરો"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"સૂચના નિયંત્રણો"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"સૂચના સ્નૂઝ કરવાના વિકલ્પો"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index c22d234bedd9..5522605cd0f4 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"स्क्रीन भरने के लिए ज़ूम करें"</string> <string name="compat_mode_off" msgid="7682459748279487945">"स्क्रीन भरने के लिए खींचें"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"स्क्रीनशॉट"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"इमेज डाली गई"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"स्क्रीनशॉट सहेजा जा रहा है..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"स्क्रीनशॉट सहेजा जा रहा है..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव किया गया"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"रिकॉर्डिंग शुरू करें"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"वॉइसओवर रिकॉर्ड करें"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"टैप दिखाएं"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"रोकने के लिए टैप करें"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"रोकें"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"रोकें"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"फिर से शुरू करें"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"बैटरी सेवर"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"शाम को चालू होगा"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"सुबह तक चालू रहेगी"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> बजे चालू हाेगी"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> बजे तक चालू रहेगी"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"एनएफ़सी"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC बंद है"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC चालू है"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"स्क्रीन रिकॉर्ड"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"शुरू करें"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"रोकें"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ऐप्लिकेशन बदलने के लिए ऊपर स्वाइप करें"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ऐप्लिकेशन को झटपट स्विच करने के लिए उसे दाईं ओर खींचें और छोड़ें"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"खास जानकारी टॉगल करें"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"पसंद के मुताबिक बनाएं"</string> <string name="notification_done" msgid="6215117625922713976">"हो गया"</string> <string name="inline_undo" msgid="9026953267645116526">"पहले जैसा करें"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"इस सूचना को \'बातचीत नहीं\' के रूप में मार्क करें"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"पसंदीदा के रूप में मार्क करें"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"पसंदीदा से हटाएं"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"म्यूट करें"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"अनम्यूट करें"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"बबल के रूप में दिखाएं"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"बबल्स को बंद करें"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"होम स्क्रीन पर जोड़ें"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"सूचना नियंत्रण"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"सूचना को स्नूज़ (थोड़ी देर के लिए चुप करना) करने के विकल्प"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 128670923f1c..55a0e788581a 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zumiraj i ispuni zaslon"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Rastegni i ispuni zaslon"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Snimka zaslona"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Umetnuta je slika"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Spremanje snimke zaslona..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Spremanje snimke zaslona..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Snimka zaslona spremljena"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Započni snimanje"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Snimi glasovni zapis"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Prikaži dodire"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Dodirnite da biste zaustavili"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Zaustavi"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pauza"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Nastavi"</string> @@ -390,6 +392,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Štednja baterije"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Uključuje se u suton"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do izlaska sunca"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Uključuje se u <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je onemogućen"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je omogućen"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index a3b6d3fe1480..a9b2d036ebcc 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Nagyítás a kitöltéshez"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Nyújtás kitöltéshez"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Képernyőkép"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Kép beszúrva"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Képernyőkép mentése..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Képernyőkép mentése..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"A képernyőkép mentése sikerült"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Rögzítés indítása"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Hang rögzítése"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Koppintások megjelenítése"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Koppintson a leállításhoz"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Leállítás"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Szünet"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Folytatás"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Akkumulátorkímélő"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Be: napnyugta"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Napfelkeltéig"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Be: <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Eddig: <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Az NFC ki van kapcsolva"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Az NFC be van kapcsolva"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 5c73ed18d73f..78acb2ed164e 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Խոշորացնել` էկրանը լցնելու համար"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Ձգել` էկրանը լցնելու համար"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Սքրինշոթ"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Պատկերը զետեղվեց"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Սքրինշոթը պահվում է…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Սքրինշոթը պահվում է..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Սքրինշոթը պահվեց"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Սկսել տեսագրումը"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Ձայնագրել ուղեկցող ձայները"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Ցույց տալ հպումները"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Հպեք՝ դադարեցնելու համար"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Կանգնեցնել"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Դադարեցնել"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Վերսկսել"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Մարտկոցի տնտեսում"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Կմիացվի մայրամուտին"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Մինչև լուսաբաց"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Կմիանա՝ <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Մինչև <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC-ն անջատված է"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC-ն միացված է"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Էկրանի ձայնագրում"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Սկսել"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Կանգնեցնել"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Սահեցրեք վերև՝ մյուս հավելվածին անցնելու համար"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Քաշեք աջ՝ հավելվածների միջև անցնելու համար"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Միացնել/անջատել համատեսքը"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Կարգավորել"</string> <string name="notification_done" msgid="6215117625922713976">"Պատրաստ է"</string> <string name="inline_undo" msgid="9026953267645116526">"Հետարկել"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Նշել այս ծանուցումը որպես ոչ խոսակցություն"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Ավելացնել ընտրանիում"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Հեռացնել ընտրանուց"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Անջատել ծանուցումները"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Միացնել ծանուցումները"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Ցուցադրել որպես ամպիկ"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Անջատել ամպիկները"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Ավելացնել հիմնական էկրանին"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"ծանուցման կառավարներ"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ծանուցման հետաձգման ընտրանքներ"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index bad3e677af0d..95c16c87dc1a 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Perbesar utk mengisi layar"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Rentangkn utk mngisi layar"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Gambar disisipkan"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Menyimpan screenshot..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Menyimpan screenshot..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot disimpan"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Mulai Merekam"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Rekam voiceover"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Tampilkan sentuhan"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Ketuk untuk menghentikan"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stop"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Jeda"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Lanjutkan"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Penghemat Baterai"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Aktif saat malam"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Sampai pagi"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aktif pada <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Sampai <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC dinonaktifkan"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC diaktifkan"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 68f04fb506fc..985f62d3e936 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Fylla skjá með aðdrætti"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Teygja yfir allan skjáinn"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Skjámynd"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Mynd sett inn"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Vistar skjámynd…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Vistar skjámynd…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skjámynd vistuð"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Hefja upptöku"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Taka upp talsetningu"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Sýna snertingar"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Ýttu til að stöðva"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stöðva"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Hlé"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Halda áfram"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Rafhlöðusparnaður"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Kveikt við sólsetur"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Til sólarupprásar"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Virkt kl. <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Til <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Slökkt á NFC"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Kveikt á NFC"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index bdf93fa93e15..870e6b7a75e7 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom per riempire schermo"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Estendi per riemp. schermo"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Immagine inserita"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Salvataggio screenshot..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Salvataggio screenshot..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot salvato"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Avvia registrazione"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Registra voce fuori campo"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Mostra tocchi"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tocca per interrompere"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Interrompi"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pausa"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Riprendi"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Risparmio energetico"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Attivato al tramonto"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Fino all\'alba"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Attivazione alle <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Fino alle <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC non attiva"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC attiva"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 9cfd0b3a22f6..775be02e20e9 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"הגדל תצוגה כדי למלא את המסך"</string> <string name="compat_mode_off" msgid="7682459748279487945">"מתח כדי למלא את המסך"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"צילום מסך"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"התמונה התווספה"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"שומר צילום מסך..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"שומר צילום מסך..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"צילום המסך נשמר"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"הפעלת ההקלטה"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"הקלטת voiceover"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"הצגת הקשות"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"אפשר להקיש כדי להפסיק"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"עצירה"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"השהיה"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"המשך"</string> @@ -392,15 +394,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"חיסכון בסוללה"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"מופעל בשקיעה"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"עד הזריחה"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"יתחיל בשעה <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"עד <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC מושבת"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC מופעל"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"הקלטת המסך"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"התחלה"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"עצירה"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"יש להחליק מעלה כדי להחליף אפליקציות"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"יש לגרור ימינה כדי לעבור במהירות בין אפליקציות"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"החלפת מצב של מסכים אחרונים"</string> @@ -702,22 +703,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"התאמה אישית"</string> <string name="notification_done" msgid="6215117625922713976">"סיום"</string> <string name="inline_undo" msgid="9026953267645116526">"ביטול"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"סימון ההתראה הזו כהתראה שאינה משיחה"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"סימון כמועדפת"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"הסרה מהמועדפים"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"השתקה"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"ביטול ההשתקה"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"הצגה בתור בועה"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"כיבוי הבועות"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"הוספה למסך הבית"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"בקרת התראות"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"אפשרויות של דחיית התראות לטיפול בהמשך"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 0c57372f3ba9..7af3f8631f03 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"画面サイズに合わせて拡大"</string> <string name="compat_mode_off" msgid="7682459748279487945">"画面サイズに合わせて拡大"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"スクリーンショット"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"画像を挿入しました"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"スクリーンショットを保存中..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"スクリーンショットを保存しています..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"スクリーンショットを保存しました"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"録画を開始"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"ナレーションの録音"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"タップの表示"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"タップして停止"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"停止"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"一時停止"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"再開"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"バッテリー セーバー"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"日の入りに ON"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"日の出まで"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>にオン"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g>まで"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC は無効です"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC は有効です"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"スクリーン レコード"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"開始"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"停止"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"アプリを切り替えるには上にスワイプ"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"右にドラッグするとアプリを素早く切り替えることができます"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"概要を切り替え"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"カスタマイズ"</string> <string name="notification_done" msgid="6215117625922713976">"完了"</string> <string name="inline_undo" msgid="9026953267645116526">"元に戻す"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"この通知を会話ではないとマーク"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"お気に入り"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"お気に入りから削除"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"ミュート"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"ミュートを解除"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"ふきだしとして表示"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"ふきだしを OFF にする"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"ホーム画面に追加"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"通知管理"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"通知スヌーズ設定"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index a3827d2a183b..2433f9e32e8f 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"მასშტაბი შეცვალეთ ეკრანის შესავსებად."</string> <string name="compat_mode_off" msgid="7682459748279487945">"გაწიეთ ეკრანის შესავსებად."</string> <string name="global_action_screenshot" msgid="2760267567509131654">"ეკრანის ანაბეჭდი"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"სურათი ჩასმულია"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"სკრინშოტის შენახვა…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ეკრანის სურათის შენახვა…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ეკრანის ანაბეჭდი შენახულია"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"ჩაწერის დაწყება"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"ხმის ჩაწერა"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"შეხებების ჩვენება"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"შეეხეთ შესაწყვეტად"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"შეწყვეტა"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"პაუზა"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"გაგრძელება"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ბატარეის დამზოგი"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ჩაირთოს მზის ჩასვლისას"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"მზის ამოსვლამდე"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"ჩაირთოს <xliff:g id="TIME">%s</xliff:g>-ზე"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g>-მდე"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC გათიშულია"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ჩართულია"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ეკრანის ჩანაწერი"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"დაწყება"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"შეწყვეტა"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"გადაფურცლეთ ზემოთ აპების გადასართავად"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"აპების სწრაფად გადასართავად ჩავლებით გადაიტანეთ მარჯვნივ"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"მიმოხილვის გადართვა"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"მორგება"</string> <string name="notification_done" msgid="6215117625922713976">"მზადაა"</string> <string name="inline_undo" msgid="9026953267645116526">"მოქმედების გაუქმება"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"ამ შეტყობინების მონიშვნა, როგორც არა მიმოწერის"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"რჩეული"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"რჩეულებიდან ამოღება"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"დადუმება"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"დადუმების მოხსნა"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"ბუშტის სახით ჩვენება"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"ბუშტების გამორთვა"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"მთავარ ეკრანზე დამატება"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"შეტყობინებების მართვის საშუალებები"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"შეტყობინებების ჩაჩუმების ვარიანტები"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 19ae4e2a02d7..be93c18006b9 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Экранды толтыру үшін ұлғайту"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Экранды толтыру үшін созу"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Скриншот"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Сурет енгізілді"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Скриншотты сақтауда…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Скриншотты сақтауда…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сақталды"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Жазуды бастау"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Кадр сыртындағы дыбысты жазу"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Түрту әрекеттерін көрсету"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Тоқтату үшін түртіңіз"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Тоқтату"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Тоқтата тұру"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Жалғастыру"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Battery Saver"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Күн батқанда қосу"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Күн шыққанға дейін"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Қосылу уақыты: <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> дейін"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC өшірулі"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC қосулы"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Экранды жазу"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Бастау"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Тоқтату"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Қолданбалар арасында ауысу үшін жоғары сырғытыңыз"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Қолданбаларды жылдам ауыстырып қосу үшін оңға қарай сүйреңіз"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Шолуды қосу/өшіру"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Реттеу"</string> <string name="notification_done" msgid="6215117625922713976">"Дайын"</string> <string name="inline_undo" msgid="9026953267645116526">"Қайтару"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Хабарландыруды сөйлесу емес деп белгілеу"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Таңдаулы"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Таңдаулылар тізімінен шығару"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Дыбысын өшіру"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Дыбысын қосу"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Қалқымалы анықтама ретінде көрсету"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Қалқымалы анықтамаларды өшіру"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Негізгі экранға қосу"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"хабарландыруларды басқару элементтері"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"хабарландыруды кідірту опциялары"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index d6ec177d3909..413125cefb92 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"ពង្រីកដើម្បីឲ្យពេញអេក្រង់"</string> <string name="compat_mode_off" msgid="7682459748279487945">"ទាញដើម្បីឲ្យពេញអេក្រង់"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"រូបថតអេក្រង់"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"បានបញ្ចូលរូបភាព"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"កំពុងរក្សាទុករូបថតអេក្រង់…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"កំពុងរក្សាទុករូបថតអេក្រង់..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"បានរក្សាទុករូបថតអេក្រង់"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"ចាប់ផ្តើមថត"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"ថតការបញ្ចូលសំឡេង"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"បង្ហាញការចុច"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"ចុច ដើម្បីបញ្ឈប់"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"ឈប់"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"ផ្អាក"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"បន្ត"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"កម្មវិធីសន្សំថ្ម"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"បើកនៅពេលថ្ងៃលិច"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"រហូតដល់ពេលថ្ងៃរះ"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"បើកនៅម៉ោង <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"រហូតដល់ម៉ោង <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"បានបិទ NFC"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"បានបើក NFC"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ការថតអេក្រង់"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ចាប់ផ្ដើម"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ឈប់"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"អូសឡើងលើដើម្បីប្តូរកម្មវិធី"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"អូសទៅស្ដាំដើម្បីប្ដូរកម្មវិធីបានរហ័ស"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"បិទ/បើកទិដ្ឋភាពរួម"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"ប្ដូរតាមបំណង"</string> <string name="notification_done" msgid="6215117625922713976">"រួចរាល់"</string> <string name="inline_undo" msgid="9026953267645116526">"ត្រឡប់វិញ"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"សម្គាល់ការជូនដំណឹងនេះថាមិនមែនជាការសន្ទនា"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"សំណព្វ"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"ដកចេញពីសំណព្វ"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"បិទសំឡេង"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"បើកសំឡេង"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"បង្ហាញជាសារលេចឡើង"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"បិទសារលេចឡើង"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"បញ្ចូលទៅក្នុងអេក្រង់ដើម"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"ការគ្រប់គ្រងការជូនដំណឹង"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ជម្រើសផ្អាកការជូនដំណឹង"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index fc0f8e6c8093..0b98e0655a7e 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"ಪರದೆ ತುಂಬಿಸಲು ಝೂಮ್ ಮಾಡು"</string> <string name="compat_mode_off" msgid="7682459748279487945">"ಪರದೆ ತುಂಬಿಸಲು ವಿಸ್ತಾರಗೊಳಿಸು"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"ಸ್ಕ್ರೀನ್ಶಾಟ್"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"ಚಿತ್ರವನ್ನು ಸೇರಿಸಲಾಗಿದೆ"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲಾಗಿದೆ"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"ರೆಕಾರ್ಡಿಂಗ್ ಆರಂಭಿಸಿ"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"voiceover ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"ಟ್ಯಾಪ್ಗಳನ್ನು ತೋರಿಸಿ"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"ನಿಲ್ಲಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"ನಿಲ್ಲಿಸಿ"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"ವಿರಾಮಗೊಳಿಸಿ"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"ಮುಂದುವರಿಸಿ"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ಬ್ಯಾಟರಿ ಸೇವರ್"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ಸೂರ್ಯಾಸ್ತ ಸಮಯದಲ್ಲಿ"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ಸೂರ್ಯೋದಯದವರೆಗೆ"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> ಸಮಯದಲ್ಲಿ"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> ವರೆಗೂ"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ನಿಷ್ಕ್ರಿಯಗೊಂಡಿದೆ"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ಸಕ್ರಿಯಗೊಂಡಿದೆ"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡ್"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ಪ್ರಾರಂಭಿಸಿ"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ನಿಲ್ಲಿಸಿ"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ಅಪ್ಲಿಕೇಶನ್ಗಳನ್ನು ಬದಲಿಸಲು ತ್ವರಿತವಾಗಿ ಬಲಕ್ಕೆ ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ಟಾಗಲ್ ನ ಅವಲೋಕನ"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"ಕಸ್ಟಮೈಜ್ ಮಾಡಿ"</string> <string name="notification_done" msgid="6215117625922713976">"ಮುಗಿದಿದೆ"</string> <string name="inline_undo" msgid="9026953267645116526">"ರದ್ದುಮಾಡಿ"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"ಈ ಅಧಿಸೂಚನೆಯನ್ನು ಸಂಭಾಷಣೆಯಲ್ಲ ಎಂಬುದಾಗಿ ಗುರುತಿಸಿ"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"ಮೆಚ್ಚಿನದು"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"ಮೆಚ್ಚಿನದಲ್ಲದ್ದು"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"ಮ್ಯೂಟ್ ಮಾಡಿ"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"ಅನ್ಮ್ಯೂಟ್ ಮಾಡಿ"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"ಗುಳ್ಳೆಯಾಗಿ ತೋರಿಸಿ"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"ಗುಳ್ಳೆಗಳನ್ನು ಆಫ್ ಮಾಡಿ"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"ಮುಖಪುಟದ ಪರದೆಗೆ ಸೇರಿಸಿ"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"ಅಧಿಸೂಚನೆ ನಿಯಂತ್ರಣಗಳು"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ಅಧಿಸೂಚನೆ ಸ್ನೂಜ್ ಆಯ್ಕೆಗಳು"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index eb3ebc918edb..54b055dd674b 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"전체화면 모드로 확대"</string> <string name="compat_mode_off" msgid="7682459748279487945">"전체화면 모드로 확대"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"스크린샷"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"이미지 삽입됨"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"캡쳐화면 저장 중..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"캡쳐화면 저장 중..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"스크린샷 저장됨"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"녹화 시작"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"음성 해설 녹음"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"탭한 항목 표시"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"탭하여 중지하기"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"중지"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"일시중지"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"재개"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"절전 모드"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"일몰에"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"일출까지"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>에"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g>까지"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 사용 중지됨"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 사용 설정됨"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"화면 녹화"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"시작"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"중지"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"위로 스와이프하여 앱 전환"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"앱을 빠르게 전환하려면 오른쪽으로 드래그"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"최근 사용 버튼 전환"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"맞춤설정"</string> <string name="notification_done" msgid="6215117625922713976">"완료"</string> <string name="inline_undo" msgid="9026953267645116526">"실행취소"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"이 알림을 대화가 아닌 일반 알림으로 표시"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"즐겨찾기"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"즐겨찾기 해제"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"숨기기"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"숨기기 취소"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"버블로 표시"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"버블 사용 중지"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"홈 화면에 추가"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"알림 관리"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"알림 일시 중지 옵션"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 06f81614d1e2..ed6b9186cdba 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Экрнд тлтр ү. чен өлч өзг"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Экранды толтуруу ү-н чоюу"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Скриншот"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Сүрөт киргизилди"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Скриншот сакталууда…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Скриншот сакталууда..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сакталды"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Жаздырып баштоо"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Үн коштоону жаздыруу"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Басылган жерди көрсөтүү"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Токтотуш үчүн басыңыз"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Токтотуу"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Тындыруу"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Улантуу"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Батареяны үнөмдөгүч"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Күн батканда күйөт"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Күн чыкканга чейин"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Саат <xliff:g id="TIME">%s</xliff:g> күйөт"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> чейин"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC өчүрүлгөн"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC иштетилген"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Экранды жаздыруу"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Старт"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Токтотуу"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Колдонмолорду которуштуруу үчүн өйдө сүрүңүз"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Колдонмолорду тез которуштуруу үчүн оңго сүйрөңүз"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Сереп салууну өчүрүү/күйгүзүү"</string> @@ -444,7 +445,7 @@ <string name="guest_new_guest" msgid="962155336259570156">"Конок кошуу"</string> <string name="guest_exit_guest" msgid="4030840507598850886">"Конокту алып салуу"</string> <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"Конокту алып саласызбы?"</string> - <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана дайындар жок кылынат."</string> + <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана дайындар өчүрүлөт."</string> <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"Алып салуу"</string> <string name="guest_wipe_session_title" msgid="7147965814683990944">"Кайтып келишиңиз менен, конок!"</string> <string name="guest_wipe_session_message" msgid="3393823610257065457">"Сеансыңызды улантасызбы?"</string> @@ -464,7 +465,7 @@ <item quantity="one">Бир колдонуучуну гана кошууга болот.</item> </plurals> <string name="user_remove_user_title" msgid="9124124694835811874">"Колдонуучу алынып салынсынбы?"</string> - <string name="user_remove_user_message" msgid="6702834122128031833">"Бул колдонуучунун бардык колдонмолору жана дайындары жок кылынат."</string> + <string name="user_remove_user_message" msgid="6702834122128031833">"Бул колдонуучунун бардык колдонмолору жана дайындары өчүрүлөт."</string> <string name="user_remove_user_remove" msgid="8387386066949061256">"Алып салуу"</string> <string name="battery_saver_notification_title" msgid="8419266546034372562">"Батареяны үнөмдөгүч режими күйүк"</string> <string name="battery_saver_notification_text" msgid="2617841636449016951">"Иштин майнаптуулугун начарлатып, фондук дайындарды чектейт"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Ыңгайлаштыруу"</string> <string name="notification_done" msgid="6215117625922713976">"Бүттү"</string> <string name="inline_undo" msgid="9026953267645116526">"Кайтаруу"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Бул билдирме \"жазышуу эмес\" катары белгиленсин"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Сүйүктүүлөргө кошуу"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Сүйүктүүлөрдөн чыгаруу"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Үнүн басуу"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Үнүн чыгаруу"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Көбүк катары көрсөтүү"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Көбүктөрдү өчүрүү"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Башкы экранга кошуу"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"эскертмелерди башкаруу каражаттары"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"эскертмени тындыруу опциялары"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 3013e1e94c34..11c4f7c45433 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"ຊູມໃຫ້ເຕັມໜ້າຈໍ"</string> <string name="compat_mode_off" msgid="7682459748279487945">"ປັບໃຫ້ເຕັມໜ້າຈໍ"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"ພາບໜ້າຈໍ"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"ແຊກຮູບແລ້ວ"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"ກຳລັງບັນທຶກຮູບໜ້າຈໍ"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ກຳລັງບັນທຶກພາບໜ້າຈໍ..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ບັນທຶກຮູບໜ້າຈໍໄວ້ແລ້ວ"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"ເລີ່ມການບັນທຶກ"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"ບັນທຶກສຽງພາກ"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"ສະແດງການແຕະ"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"ແຕະເພື່ອຢຸດ"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"ຢຸດ"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"ຢຸດຊົ່ວຄາວ"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"ສືບຕໍ່"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ຕົວປະຢັດແບັດເຕີຣີ"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ເປີດຕອນຕາເວັນຕົກ"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ຈົນກວ່າຕາເວັນຂຶ້ນ"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"ເປີດເວລາ <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"ຈົນຮອດ <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is disabled"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is enabled"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ການບັນທຶກໜ້າຈໍ"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ເລີ່ມ"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ຢຸດ"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ປັດຂື້ນເພື່ອສະຫຼັບແອັບ"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ລາກໄປຂວາເພື່ອສະຫຼັບແອັບດ່ວນ"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ສະຫຼັບພາບຮວມ"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"ປັບແຕ່ງ"</string> <string name="notification_done" msgid="6215117625922713976">"ສຳເລັດແລ້ວ"</string> <string name="inline_undo" msgid="9026953267645116526">"ຍົກເລີກ"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"ໝາຍການແຈ້ງເຕືອນນີ້ວ່າບໍ່ແມ່ນການສົນທະນາ"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"ລາຍການທີ່ມັກ"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"ຍົກເລີກລາຍການທີ່ມັກ"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"ປິດສຽງ"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"ເຊົາປິດສຽງ"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"ສະແດງເປັນ bubble"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"ປິດການໃຊ້ bubble"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"ເພີ່ມໃສ່ໜ້າຈໍຫຼັກ"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"ການຄວບຄຸມການແຈ້ງເຕືອນ"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ຕົວເລືອກການເລື່ອນການແຈ້ງເຕືອນ"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index c6b50d92e3ed..7da1174c0878 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Keisti mast., kad atit. ekr."</string> <string name="compat_mode_off" msgid="7682459748279487945">"Ištempti, kad atit. ekr."</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Ekrano kopija"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Vaizdas įterptas"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Išsaugoma ekrano kopija..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Išsaugoma ekrano kopija..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrano kopija išsaugota"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Pradėti įrašymą"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Įrašyti balsą už kadro"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Rodyti palietimus"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Sustabdykite palietę"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Sustabdyti"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pristabdyti"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Tęsti"</string> @@ -392,6 +394,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Akum. taus. priemonė"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Per saulėlydį"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Iki saulėtekio"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Iki <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"ALR"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"ALR išjungtas"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"ALR įjungtas"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index b519f053b1ce..fed86b324c78 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Tālumm., lai aizp. ekr."</string> <string name="compat_mode_off" msgid="7682459748279487945">"Stiepiet, lai aizp. ekr."</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Ekrānuzņēmums"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Attēls ir ievietots"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Saglabā ekrānuzņēmumu…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Notiek ekrānuzņēmuma saglabāšana..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrānuzņēmums saglabāts"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Sākt ierakstīšanu"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Ierakstīt balsi"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Rādīt pieskārienus"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Pieskarieties, lai apturētu"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Pārtraukt"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Apturēt"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Atsākt"</string> @@ -390,15 +392,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Jaudas taupīšana"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Saulrietā"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Līdz saullēktam"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Plkst. <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Līdz plkst. <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ir atspējoti"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ir iespējoti"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekrāna ierakstīšana"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Sākt"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Apturēt"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Velciet augšup, lai pārslēgtu lietotnes"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Lai ātri pārslēgtu lietotnes, velciet pa labi"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Pārskata pārslēgšana"</string> @@ -699,22 +700,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Pielāgot"</string> <string name="notification_done" msgid="6215117625922713976">"Gatavs"</string> <string name="inline_undo" msgid="9026953267645116526">"Atsaukt"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Atzīmēt, ka šis paziņojums nav saruna"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Pievienot izlasei"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Noņemt no izlases"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Izslēgt skaņu"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Ieslēgt skaņu"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Rādīt kā burbuli"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Izslēgt burbuļus"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Pievienot sākuma ekrānam"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"paziņojumu vadīklas"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"paziņojumu atlikšanas opcijas"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 9080b576d4a9..a02f38ed267b 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Зумирај да се исполни екранот"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Растегни да се исполни екранот"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Слика од екранот"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Вметната е слика"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Сликата на екранот се зачувува..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Сликата на екранот се зачувува..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Сликата од екранот е зачувана"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Започни со снимање"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Снимај коментар"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Прикажувај допири"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Допрете за запирање"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Сопри"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Паузирај"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Продолжи"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Штедач на батерија"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Вклуч. на зајдисонце"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"До изгрејсонце"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Се вклучува во <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"До <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC е оневозможено"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC е овозможено"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index b761c445297f..2d3f73fc5978 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"സ്ക്രീനിൽ ഉൾക്കൊള്ളിക്കാൻ സൂം ചെയ്യുക"</string> <string name="compat_mode_off" msgid="7682459748279487945">"സ്ക്രീനിൽ ഉൾക്കൊള്ളിക്കാൻ വലിച്ചുനീട്ടുക"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"സ്ക്രീൻഷോട്ട്"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"ചിത്രം ചേർത്തു"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നു..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നു..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"സ്ക്രീൻഷോട്ട് സംരക്ഷിച്ചു"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"റെക്കോര്ഡിംഗ് ആരംഭിക്കുക"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"വോയ്സ് ഓവർ റെക്കോർഡ് ചെയ്യുക"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"ടാപ്പുകൾ കാണിക്കുക"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"നിർത്താൻ ടാപ്പ് ചെയ്യുക"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"നിർത്തുക"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"താൽക്കാലികമായി നിർത്തുക"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"പുനരാരംഭിക്കുക"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ബാറ്ററി ലാഭിക്കൽ"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"സൂര്യാസ്തമയത്തിന്"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"സൂര്യോദയം വരെ"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>-ന്"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> വരെ"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC പ്രവർത്തനരഹിതമാക്കി"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC പ്രവർത്തനക്ഷമമാക്കി"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"സ്ക്രീൻ റെക്കോർഡ്"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ആരംഭിക്കുക"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"നിര്ത്തുക"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ആപ്പുകൾ മാറാൻ മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ആപ്പുകൾ പെട്ടെന്ന് മാറാൻ വലത്തോട്ട് വലിച്ചിടുക"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"അവലോകനം മാറ്റുക"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"ഇഷ്ടാനുസൃതമാക്കുക"</string> <string name="notification_done" msgid="6215117625922713976">"പൂർത്തിയായി"</string> <string name="inline_undo" msgid="9026953267645116526">"പഴയപടിയാക്കുക"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"ഈ അറിയിപ്പ് സംഭാഷണമല്ലെന്ന് അടയാളപ്പെടുത്തുക"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"പ്രിയപ്പെട്ടവ"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"പ്രിയപ്പെട്ടതല്ലാതാക്കുക"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"മ്യൂട്ട് ചെയ്യുക"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"അൺമ്യൂട്ട് ചെയ്യുക"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"ബബ്ൾ ആയി കാണിക്കുക"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"ബബിളുകൾ ഓഫാക്കുക"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"ഹോം സ്ക്രീനിലേക്ക് ചേർക്കുക"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"അറിയിപ്പ് നിയന്ത്രണങ്ങൾ"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"അറിയിപ്പ് സ്നൂസ് ഓപ്ഷനുകൾ"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 4f9b447bcd69..85d7eadce349 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Дэлгэц дүүргэх бол өсгөнө үү"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Дэлгэц дүүргэх бол татна уу"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Дэлгэцийн зураг дарах"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Зургийг орууллаа"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Дэлгэцийн агшинг хадгалж байна…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Дэлгэцийн агшинг хадгалж байна…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Дэлгэцээс дарсан зургийг хадгалсан"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Бичлэгийг эхлүүлэх"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Дуу оруулалтыг бичих"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Товшилтыг харуулах"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Зогсоохын тулд товшино уу"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Зогсоох"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Түр зогсоох"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Үргэлжлүүлэх"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Батарей хэмнэгч"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Нар жаргах үед"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Нар мандах хүртэл"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>-д"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> хүртэл"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC-г цуцалсан"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC-г идэвхжүүлсэн"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Дэлгэцийн бичлэг хийх"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Эхлүүлэх"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Зогсоох"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Апп сэлгэхийн тулд дээш шударна уу"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Аппуудыг хурдан сэлгэхийн тулд баруун тийш чирнэ үү"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Тоймыг унтраах/асаах"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Тохируулах"</string> <string name="notification_done" msgid="6215117625922713976">"Дууссан"</string> <string name="inline_undo" msgid="9026953267645116526">"Болих"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Энэ мэдэгдлийг харилцаа биш гэж тэмдэглэх"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Дуртай"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Дургүй"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Дууг хаах"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Дууг нээх"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Хөөс маягаар харуулах"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Хөөсийг унтраах"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Үндсэн нүүрэнд нэмэх"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"мэдэгдлийн удирдлага"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"мэдэгдэл түр хойшлуулагчийн сонголт"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 99f48e6b4cb9..3406edf3f1bd 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"स्क्रीन भरण्यासाठी झूम करा"</string> <string name="compat_mode_off" msgid="7682459748279487945">"स्क्रीन भरण्यासाठी ताणा"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"स्क्रीनशॉट"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"इमेज घातली गेली"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"स्क्रीनशॉट सेव्ह करत आहे…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"स्क्रीनशॉट सेव्ह करत आहे…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव्ह केला"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"रेकॉर्डिंग सुरू करा"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"व्हॉइसओव्हर रेकॉर्ड करा"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"टॅप दाखवा"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"थांबवण्यासाठी टॅप करा"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"थांबवा"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"थांबवा"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"पुन्हा सुरू करा"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"बॅटरी सेव्हर"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"संध्याकाळी सुरू होते"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"सूर्योदयापर्यंत"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> वाजता सुरू होते"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> पर्यंत"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC अक्षम केले आहे"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC सक्षम केले आहे"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"स्क्रीन रेकॉर्ड"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"सुरू"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"थांबा"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"अॅप्स स्विच करण्यासाठी वर स्वाइप करा"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"अॅप्स वर झटपट स्विच करण्यासाठी उजवीकडे ड्रॅग करा"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"अवलोकन टॉगल करा."</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"कस्टमाइझ करा"</string> <string name="notification_done" msgid="6215117625922713976">"पूर्ण झाले"</string> <string name="inline_undo" msgid="9026953267645116526">"पहिल्यासारखे करा"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"ही सूचना संभाषण नाही म्हणून खूण करा"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"आवडते"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"नावडते"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"म्यूट करा"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"अनम्यूट करा"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"बबल असे दाखवा"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"बुडबुडे बंद करा"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"होम स्क्रीनवर जोडा"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"सूचना नियंत्रणे"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"सूचना स्नूझ पर्याय"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 42c51b7844a6..eeaaceae0669 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zum untuk memenuhi skrin"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Regang utk memenuhi skrin"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Tangkapan skrin"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Imej disisipkan"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Menyimpan tangkapan skrin..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Menyimpan tangkapan skrin..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Tangkapan skrin disimpan"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Mula Merakam"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Rakam suara latar"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Tunjukkan ketikan"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Ketik untuk berhenti"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Berhenti"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Jeda"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Sambung semula"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Penjimat Bateri"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Dihidupkan pd senja"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hingga matahari trbt"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Dihidupkan pada <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Hingga <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC dilumpuhkan"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC didayakan"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Saring Rekod"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Mula"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Berhenti"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Leret ke atas untuk menukar apl"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Seret ke kanan untuk beralih apl dengan pantas"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Togol Ikhtisar"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Sesuaikan"</string> <string name="notification_done" msgid="6215117625922713976">"Selesai"</string> <string name="inline_undo" msgid="9026953267645116526">"Buat asal"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Tandai pemberitahuan ini sebagai bukan perbualan"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Kegemaran"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Nyahgemari"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Redam"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Nyahredam"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Tunjukkan sebagai gelembung"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Matikan gelembung"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Tambahkan pada skrin utama"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"kawalan pemberitahuan"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"pilihan tunda pemberitahuan"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index dd5acfab0c21..5ae82eafbcf9 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"ဇူးမ်အပြည့်ဆွဲခြင်း"</string> <string name="compat_mode_off" msgid="7682459748279487945">"ဖန်သားပြင်အပြည့်ဆန့်ခြင်း"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"ဖန်သားပြင်ဓာတ်ပုံ"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"ပုံ ထည့်ထားသည်"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"ဖန်သားပြင်ဓါတ်ပုံသိမ်းစဉ်.."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ဖန်သားပြင်ဓါတ်ပုံရိုက်ခြင်းအား သိမ်းဆည်းပါမည်"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ဖန်သားပြင်ဓာတ်ပုံကို သိမ်းပြီးပါပြီ"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"စတင် ရိုက်ကူးရန်"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"နောက်ခံစကားပြော ကူးယူရန်"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"တို့ခြင်းများကို ပြရန်"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"ရပ်ရန် တို့ပါ"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"ရပ်ရန်"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"ခဏရပ်ရန်"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"ဆက်လုပ်ရန်"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ဘက်ထရီ အားထိန်း"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"နေဝင်ချိန်၌ ဖွင့်ရန်"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"နေထွက်ချိန် အထိ"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> တွင် ဖွင့်မည်"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> အထိ"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ကို ပိတ်ထားသည်"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ကို ဖွင့်ထားသည်"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ဖန်သားပြင် မှတ်တမ်းတင်ရန်"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"စတင်ရန်"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ရပ်ရန်"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"အက်ပ်များကို ဖွင့်ရန် အပေါ်သို့ ပွတ်ဆွဲပါ"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"အက်ပ်များကို ပြောင်းရန် ညာဘက်သို့ ဖိဆွဲပါ"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ဖွင့်၊ ပိတ် အနှစ်ချုပ်"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"စိတ်ကြိုက်ပြုလုပ်ရန်"</string> <string name="notification_done" msgid="6215117625922713976">"ပြီးပါပြီ"</string> <string name="inline_undo" msgid="9026953267645116526">"တစ်ဆင့်နောက်ပြန်ရန်"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"ဤအကြောင်းကြားချက်ကို စကားဝိုင်းမဟုတ်ဟု မှတ်ထားရန်"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"အကြိုက်ဆုံး"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"အကြိုက်ဆုံးမှ ဖယ်ရှားရန်"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"အသံတိတ်ရန်"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"အသံဖွင့်ရန်"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"ပူဖောင်းကွက်အဖြစ် ပြရန်"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"ပူဖောင်းကွက်များကို ပိတ်ရန်"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"ပင်မစာမျက်နှာတွင် ထည့်ရန်"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"အကြောင်းကြားချက် ထိန်းချုပ်မှုများ"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"အကြောင်းကြားချက်များကို ဆိုင်းငံ့ရန် ရွေးချယ်စရာများ"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 97d6792ee6bb..463b95c0107e 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom for å fylle skjermen"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Strekk for å fylle skjerm"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Skjermdump"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Bildet er satt inn"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Lagrer skjermdumpen …"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Lagrer skjermdumpen …"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skjermdumpen er lagret"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Start opptak"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Ta opp et kommentarspor"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Vis trykk"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Trykk for å stoppe"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stopp"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Sett på pause"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Gjenoppta"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Batterisparing"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"På ved solnedgang"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Til soloppgang"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Slås på klokken <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Til <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC er slått av"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC er slått på"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Skjermopptak"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Start"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Stopp"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Sveip opp for å bytte apper"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Dra til høyre for å bytte apper raskt"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Slå oversikten av eller på"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Tilpass"</string> <string name="notification_done" msgid="6215117625922713976">"Ferdig"</string> <string name="inline_undo" msgid="9026953267645116526">"Angre"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Marker dette varselet som ikke en samtale"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favoritt"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Fjern som favoritt"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Ignorer"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Slutt å ignorere"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Vis som boble"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Slå av bobler"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Legg til på startskjermen"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"varselinnstillinger"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"slumrealternativer for varsler"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 1345b098f06e..21a76a3a86d0 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"स्क्रिन भर्न जुम गर्नुहोस्"</string> <string name="compat_mode_off" msgid="7682459748279487945">"स्क्रिन भर्न तन्काउनुहोस्"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"स्क्रिनसट"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"छवि सम्मिलित गरियो"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"स्क्रिनसट बचत गर्दै…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"स्क्रिनसट बचत गर्दै…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रिनसट सुरक्षित गरियो"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"रेकर्डिङ सुरु गर्नुहोस्"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"भ्वाइसओवर रेकर्ड गर्नुहोस्"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"ट्यापहरू देखाउनुहोस्"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"रोक्न ट्याप गर्नुहोस्"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"रोक्नुहोस्"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"पज गर्नुहोस्"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"जारी राख्नुहोस्"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ब्याट्री सेभर"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"सूर्यास्तमा सक्रिय"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"सूर्योदयसम्म"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> मा सक्रिय"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> सम्म"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC लाई असक्षम पारिएको छ"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC लाई सक्षम पारिएको छ"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"स्रिनको रेकर्ड"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"सुरु गर्नुहोस्"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"रोक्नुहोस्"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"अनुप्रयोगहरू बदल्न माथितिर स्वाइप गर्नुहोस्"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"अनुप्रयोगहरू बदल्न द्रुत गतिमा दायाँतिर ड्र्याग गर्नुहोस्"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"परिदृश्य टगल गर्नुहोस्"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"आफू अनुकूल पार्नुहोस्"</string> <string name="notification_done" msgid="6215117625922713976">"सम्पन्न भयो"</string> <string name="inline_undo" msgid="9026953267645116526">"अन्डू गर्नुहोस्"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"यो सूचनालाई वार्तालाप होइन भनी चिन्ह लगाउनुहोस्"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"मन पर्ने"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"मन पर्ने कुराहरूबाट हटाउनुहोस्"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"म्युट गर्नुहोस्"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"अनम्युट गर्नुहोस्"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"बबलको रूपमा देखाउनुहोस्"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"बबलहरूलाई निष्क्रिय पार्नुहोस्"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"गृह स्क्रिनमा थप्नुहोस्"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"सूचना सम्बन्धी नियन्त्रणहरू"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"सूचना स्नुज गर्ने विकल्पहरू"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 71d2388264fb..3c040154437c 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom om scherm te vullen"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Rek uit v. schermvulling"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Afbeelding ingevoegd"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Screenshot opslaan..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Screenshot opslaan..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot opgeslagen"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Opname starten"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Voice-over opnemen"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Tikken weergeven"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tik om te stoppen"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stoppen"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pauzeren"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Hervatten"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Batterijbesparing"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Aan bij zonsondergang"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Tot zonsopgang"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aan om <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Tot <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC is uitgeschakeld"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC is ingeschakeld"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 85841feeff14..42302756debd 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"ସ୍କ୍ରୀନ ଭରିବା ପାଇଁ ଜୁମ୍ କରନ୍ତୁ"</string> <string name="compat_mode_off" msgid="7682459748279487945">"ସ୍କ୍ରୀନ୍କୁ ଭରିବା ପାଇଁ ଟାଣନ୍ତୁ"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"ସ୍କ୍ରିନ୍ସଟ୍ ନିଅନ୍ତୁ"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"ଛବି ଭର୍ତ୍ତି କରାଯାଇଛି"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"ସ୍କ୍ରୀନଶଟ୍ ସେଭ୍ କରାଯାଉଛି…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ସ୍କ୍ରୀନଶଟ୍ ସେଭ୍ କରାଯାଉଛି…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ସ୍କ୍ରୀନଶଟ୍ ସେଭ୍ ହୋଇଛି"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"ରେକର୍ଡିଂ ଆରମ୍ଭ କରନ୍ତୁ"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"ଭଏସ୍ଓଭର୍ ରେକର୍ଡ କରନ୍ତୁ"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"ଟାପ୍ ଦେଖାନ୍ତୁ"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"ବନ୍ଦ କରିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"ବନ୍ଦ କରନ୍ତୁ"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"ବିରତି କରନ୍ତୁ"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"ପୁଣି ଚାଲୁ କରନ୍ତୁ"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ବ୍ୟାଟେରୀ ସେଭର୍"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ସନ୍ଧ୍ୟାରେ ଚାଲୁ ହେବ"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ସକାଳ ପର୍ଯ୍ୟନ୍ତ"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>ରେ ଚାଲୁ ହେବ"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> ପର୍ଯ୍ୟନ୍ତ"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ଅକ୍ଷମ କରାଯାଇଛି"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ସକ୍ଷମ କରାଯାଇଛି"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ସ୍କ୍ରିନ୍ ରେକର୍ଡ"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ଆରମ୍ଭ କରନ୍ତୁ"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ବନ୍ଦ କରନ୍ତୁ"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ଆପ୍କୁ ବଦଳ କରିବା ପାଇଁ ସ୍ଵାଇପ୍ କରନ୍ତୁ"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ଆପ୍ଗୁଡ଼ିକ ମଧ୍ୟରେ ଶୀଘ୍ର ବଦଳ କରିବା ପାଇଁ ଡାହାଣକୁ ଡ୍ରାଗ୍ କରନ୍ତୁ"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ସଂକ୍ଷିପ୍ତ ବିବରଣୀକୁ ଟୋଗଲ୍ କରନ୍ତୁ"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"କଷ୍ଟମାଇଜ୍ କରନ୍ତୁ"</string> <string name="notification_done" msgid="6215117625922713976">"ହୋଇଗଲା"</string> <string name="inline_undo" msgid="9026953267645116526">"ପୂର୍ବସ୍ଥାନକୁ ଫେରାଇଆଣନ୍ତୁ"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"ଏହି ବିଜ୍ଞପ୍ତି କୌଣସି ବାର୍ତ୍ତାଳାପ ନୁହେଁ ବୋଲି ଚିହ୍ନଟ କରନ୍ତୁ"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"ପସନ୍ଦ ଭାବରେ ଚିହ୍ନଟ କରନ୍ତୁ"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"ନାପସନ୍ଦ କରନ୍ତୁ"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"ମ୍ୟୁଟ୍ କରନ୍ତୁ"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"ଅନମ୍ୟୁଟ୍ କରନ୍ତୁ"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"ବୁଦବୁଦ୍ ଭାବରେ ଦେଖାନ୍ତୁ"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"ବୁଦବୁଦଗୁଡ଼ିକ ବନ୍ଦ କରନ୍ତୁ"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"ମୂଳ ସ୍କ୍ରିନରେ ଯୋଗ କରନ୍ତୁ"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"ବିଜ୍ଞପ୍ତି ନିୟନ୍ତ୍ରଣ"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ବିଜ୍ଞପ୍ତି ସ୍ନୁଜ୍ ବିକଳ୍ପ"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 4ea51ddbde9e..f345d6a8f7aa 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"ਸਕ੍ਰੀਨ ਭਰਨ ਲਈ ਜ਼ੂਮ ਕਰੋ"</string> <string name="compat_mode_off" msgid="7682459748279487945">"ਸਕ੍ਰੀਨ ਭਰਨ ਲਈ ਸਟ੍ਰੈਚ ਕਰੋ"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"ਸਕ੍ਰੀਨਸ਼ਾਟ"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"ਚਿੱਤਰ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਸੁਰੱਖਿਅਤ ਕਰ ਰਿਹਾ ਹੈ…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਸੁਰੱਖਿਅਤ ਕਰ ਰਿਹਾ ਹੈ…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"ਰਿਕਾਰਡਿੰਗ ਸ਼ੁਰੂ ਕਰੋ"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"ਅਵਾਜ਼ ਰਿਕਾਰਡ ਕਰੋ"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"ਟੈਪਾਂ ਦਿਖਾਓ"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"ਰੋਕਣ ਲਈ ਟੈਪ ਕਰੋ"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"ਬੰਦ ਕਰੋ"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"ਰੋਕੋ"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"ਮੁੜ-ਚਾਲੂ ਕਰੋ"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"ਬੈਟਰੀ ਸੇਵਰ"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"ਸੂਰਜ ਛਿਪਣ \'ਤੇ ਚਾਲੂ"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"ਸੂਰਜ ਚੜ੍ਹਨ ਤੱਕ"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> ਵਜੇ ਚਾਲੂ"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> ਵਜੇ ਤੱਕ"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ਨੂੰ ਅਯੋਗ ਬਣਾਇਆ ਗਿਆ ਹੈ"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ਨੂੰ ਯੋਗ ਬਣਾਇਆ ਗਿਆ ਹੈ"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਰ"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ਸ਼ੁਰੂ ਕਰੋ"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ਰੋਕੋ"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ਐਪਾਂ ਵਿਚਾਲੇ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ਐਪਾਂ ਵਿਚਾਲੇ ਤੇਜ਼ੀ ਨਾਲ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ ਸੱਜੇ ਪਾਸੇ ਵੱਲ ਘਸੀਟੋ"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"ਰੂਪ-ਰੇਖਾ ਨੂੰ ਟੌਗਲ ਕਰੋ"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"ਵਿਉਂਤਬੱਧ ਕਰੋ"</string> <string name="notification_done" msgid="6215117625922713976">"ਹੋ ਗਿਆ"</string> <string name="inline_undo" msgid="9026953267645116526">"ਅਣਕੀਤਾ ਕਰੋ"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"ਇਸ ਸੂਚਨਾ ਦੀ ਗੱਲ-ਬਾਤ ਨਹੀਂ ਵਜੋਂ ਨਿਸ਼ਾਨਦੇਹੀ ਕਰੋ"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"ਮਨਪਸੰਦ"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"ਨਾਪਸੰਦ"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"ਮਿਊਟ ਕਰੋ"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"ਅਣਮਿਊਟ ਕਰੋ"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"ਬੁਲਬੁਲੇ ਵਜੋਂ ਦਿਖਾਓ"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"ਬੁਲਬੁਲੇ ਬੰਦ ਕਰੋ"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"ਸੂਚਨਾ ਕੰਟਰੋਲ"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ਸੂਚਨਾ ਸਨੂਜ਼ ਵਿਕਲਪ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 515d659d2c5b..68cb14eb32b3 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Powiększ, aby wypełnić ekran"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Rozciągnij, aby wypełnić ekran"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Zrzut ekranu"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Wstawiono obraz"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Zapisywanie zrzutu ekranu..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Zapisywanie zrzutu ekranu..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Zrzut ekranu został zapisany"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Rozpocznij rejestrowanie"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Nagraj tekst lektora"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Pokaż dotknięcia"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Kliknij, by zatrzymać"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Zatrzymaj"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Wstrzymaj"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Wznów"</string> @@ -392,15 +394,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Oszczędzanie baterii"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Włącz o zachodzie"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do wschodu słońca"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Włącz o <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"Komunikacja NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Komunikacja NFC jest wyłączona"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Komunikacja NFC jest włączona"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Zapis ekranu"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Rozpocznij"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Zatrzymaj"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Przesuń w górę, by przełączyć aplikacje"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Szybko przeciągnij w prawo, by przełączyć aplikacje"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Przełącz Przegląd"</string> @@ -702,22 +703,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Dostosuj"</string> <string name="notification_done" msgid="6215117625922713976">"Gotowe"</string> <string name="inline_undo" msgid="9026953267645116526">"Cofnij"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Nie oznaczaj tego powiadomienia jako wątku"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Dodaj do ulubionych"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Usuń z ulubionych"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Wycisz"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Wyłącz wyciszenie"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Pokaż jako dymek"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Wyłącz dymki"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Dodaj do ekranu głównego"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"sterowanie powiadomieniami"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opcje odkładania powiadomień"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index d486b9b2a7ed..a9101d74706f 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom p/ preencher a tela"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Ampliar p/ preencher tela"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Capturar tela"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Imagem inserida"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Salvando captura de tela..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Salvando captura de tela..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Iniciar gravação"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Gravar narração"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Mostrar toques"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Toque para parar"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Parar"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pausar"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Retomar"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Economia de bateria"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Ativ. ao pôr do sol"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Até o nascer do sol"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ativar: <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Até: <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"A NFC está desativada"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"A NFC está ativada"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index d17b0026802f..1a6e6739e7ff 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom para preencher o ecrã"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Esticar p. caber em ec. int."</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Captura de ecrã"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Imagem inserida"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"A guardar captura de ecrã..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"A guardar captura de ecrã..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de ecrã guardada"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Iniciar gravação"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Gravar voz-off"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Mostrar toques"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tocar para parar"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Parar"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Colocar em pausa"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Retomar"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Poupança de bateria"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Ativ. ao pôr do sol"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Até ao amanhecer"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ativado à(s) <xliff:g id="TIME">%s</xliff:g>."</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Até à(s) <xliff:g id="TIME">%s</xliff:g>."</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"O NFC está desativado"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"O NFC está ativado"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index d486b9b2a7ed..a9101d74706f 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom p/ preencher a tela"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Ampliar p/ preencher tela"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Capturar tela"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Imagem inserida"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Salvando captura de tela..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Salvando captura de tela..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Iniciar gravação"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Gravar narração"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Mostrar toques"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Toque para parar"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Parar"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pausar"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Retomar"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Economia de bateria"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Ativ. ao pôr do sol"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Até o nascer do sol"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ativar: <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Até: <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"A NFC está desativada"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"A NFC está ativada"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 2674504db62e..e34fa2de54d2 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zoom pt. a umple ecranul"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Înt. pt. a umple ecranul"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Captură de ecran"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"S-a inserat imaginea"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Se salv. captura de ecran..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Se salvează captura de ecran..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Captură de ecran salvată"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Începeți înregistrarea"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Înregistrați vocal"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Afișați atingerile"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Atingeți pentru a opri"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Opriți"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Întrerupeți"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Reluați"</string> @@ -390,15 +392,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Economisire baterie"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Activată la apus"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Până la răsărit"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Activată la <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Până la <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Serviciul NFC este dezactivat"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Serviciul NFC este activat"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Înregistrarea ecranului"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Începeți"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Opriți"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Glisați în sus pentru a comuta între aplicații"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Glisați la dreapta pentru a comuta rapid între aplicații"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Comutați secțiunea Recente"</string> @@ -699,22 +700,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Personalizați"</string> <string name="notification_done" msgid="6215117625922713976">"Terminat"</string> <string name="inline_undo" msgid="9026953267645116526">"Anulați"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Marcați această notificare ca nefiind o conversație"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Preferată"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Anulați marcarea ca preferată"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Ignorați"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Afișați"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Afișează sub formă de balon"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Dezactivați baloanele"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Adăugați pe ecranul de pornire"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"comenzile notificării"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opțiuni de amânare a notificării"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 915698972541..0ee159d0c648 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Подогнать по размерам экрана"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Растянуть на весь экран"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Скриншот"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Изображение вставлено"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Сохранение..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Сохранение..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сохранен"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Начать запись"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Записать закадровую речь"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Показывать нажатия"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Нажмите, чтобы остановить"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Остановить"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Приостановить"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Возобновить"</string> @@ -392,15 +394,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Режим энергосбер."</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Вкл. на закате"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"До рассвета"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Включить в <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"До <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"Модуль NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Модуль NFC отключен"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Модуль NFC включен"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Запись экрана"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Начать"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Остановить"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Чтобы переключиться между приложениями, проведите по экрану вверх."</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Перетащите вправо, чтобы быстро переключиться между приложениями"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Переключить режим обзора"</string> @@ -702,22 +703,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Настроить"</string> <string name="notification_done" msgid="6215117625922713976">"Готово"</string> <string name="inline_undo" msgid="9026953267645116526">"Отменить"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Понизить приоритет уведомления (не считать чатом)"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Добавить в избранное"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Удалить из избранного"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Отключить звук"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Включить звук"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Показывать как всплывающее уведомление"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Отключить показ всплывающих уведомлений"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Добавить на главный экран"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g>: <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"настройки уведомлений"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"параметры отсрочки уведомлений"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 84e01d093f3d..2c5e1d8ba117 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"තිරය පිරවීමට විශාලනය කරන්න"</string> <string name="compat_mode_off" msgid="7682459748279487945">"තිරය පිරවීමට අදින්න"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"තිර රුව"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"රූපය ඇතුල් කරන ලදී"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"තිර රුව සුරකිමින්…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"තිර රුව සුරැකෙමින් පවතී…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"තිර රුව සුරකින ලදී"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"පටිගත කිරීම ආරම්භ කරන්න"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"පසුබිම් කථනය පටිගත කරන්න"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"තට්ටු කිරීම් පෙන්වන්න"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"නතර කිරීමට තට්ටු කරන්න"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"නතර කරන්න"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"විරාම කරන්න"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"නැවත අරඹන්න"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"බැටරි සුරැකුම"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"හිරු බැසීමේදී ඔන්ය"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"හිරු නගින තෙක්"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>ට ක්රියාත්මකයි"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> තෙක්"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC අබලයි"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC සබලයි"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"තිර පටිගත කිරීම"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ආරම්භ කරන්න"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"නතර කරන්න"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"යෙදුම් මාරු කිරීමට ස්වයිප් කරන්න"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ඉක්මනින් යෙදුම් මාරු කිරීමට දකුණට අදින්න"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"දළ විශ්ලේෂණය ටොගල කරන්න"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"අභිරුචිකරණය"</string> <string name="notification_done" msgid="6215117625922713976">"නිමයි"</string> <string name="inline_undo" msgid="9026953267645116526">"පසුගමනය කරන්න"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"මෙම දැනුම් දීම සංවාදයක් නොවේ ලෙස ලකුණු කරන්න"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"ප්රියතම"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"ප්රියතම වෙතින් ඉවත් කරන්න"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"නිහඬ කරන්න"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"නිහඬ වෙතින් ඉවත් කරන්න"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"බුබුලක් ලෙස පෙන්වන්න"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"බුබුලු ක්රියාවිරහිත කරන්න"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"මුල් තිරය වෙත එක් කරන්න"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"දැනුම්දීම් පාලන"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"දැනුම්දීම් මදක් නතර කිරීමේ විකල්ප"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index fced8a42acf4..93f93a2860a9 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Priblížiť na celú obrazovku"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Na celú obrazovku"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Snímka obrazovky"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Bol vložený obrázok"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Prebieha ukladanie snímky obrazovky..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Prebieha ukladanie snímky obrazovky..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Snímka obrazovky bola uložená"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Spustiť zaznamenávanie"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Hlasový vstup počas záznamu"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Zobrazovať klepnutia"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Zastavte klepnutím"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Ukončiť"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pozastaviť"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Obnoviť"</string> @@ -392,6 +394,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Šetrič batérie"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Zap. pri záp. slnka"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do východu slnka"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Zapne sa o <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je deaktivované"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je aktivované"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index 4816a6634dd7..452827fa362f 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Povečava čez cel zaslon"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Raztegnitev čez zaslon"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Posnetek zaslona"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Slika je vstavljena"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Shranjev. posnetka zaslona ..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Shranjevanje posnetka zaslona ..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Posnetek zaslona je shranjen"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Začni snemanje"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Snemanje spremnega govora"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Prikaz dotikov"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Dotaknite se, da ustavite"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Ustavi"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Začasno ustavi"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Nadaljuj"</string> @@ -392,15 +394,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Varč. z ener. bater."</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Ob sončnem zahodu"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Do sončnega vzhoda"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Vklop ob <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Do <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Tehnologija NFC je onemogočena"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Tehnologija NFC je omogočena"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Snemanje zaslona"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Začni"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ustavi"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Za preklop aplikacij povlecite navzgor"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Povlecite v desno za hiter preklop med aplikacijami"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Vklop/izklop pregleda"</string> @@ -702,22 +703,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Prilagodi"</string> <string name="notification_done" msgid="6215117625922713976">"Dokončano"</string> <string name="inline_undo" msgid="9026953267645116526">"Razveljavi"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Označi, da to obvestilo ni pogovor"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Priljubljeno"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Odstrani iz priljubljenih"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Prezri"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Znova prikaži"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Pokaži kot oblaček"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Izklopi oblačke"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Dodaj na začetni zaslon"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"kontrolniki obvestil"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"možnosti preložitve obvestil"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 14d72b4e1675..2dc3cc8b1cd2 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zmadho për të mbushur ekranin"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Shtrije për të mbushur ekranin"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Pamja e ekranit"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Imazhi është futur"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Po ruan pamjen e ekranit..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Po ruan pamjen e ekranit…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Pamja e ekranit u ruajt"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Nis regjistrimin"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Regjistro zërin e mikrofonit"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Shfaq trokitjet"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Trokit për të ndaluar"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Ndalo"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Ndërprit"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Vazhdo"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Kursyesi i baterisë"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Aktiv në perëndim"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Deri në lindje të diellit"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aktiv në <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Deri në <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC është çaktivizuar"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC është aktivizuar"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Regjistrimi i ekranit"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Nis"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ndalo"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Rrëshqit shpejt lart për të ndërruar aplikacionet"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Zvarrit djathtas për të ndërruar aplikacionet me shpejtësi"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Kalo te përmbledhja"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Personalizo"</string> <string name="notification_done" msgid="6215117625922713976">"U krye"</string> <string name="inline_undo" msgid="9026953267645116526">"Zhbëj"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Shëno se ky njoftim nuk është një bisedë"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Shëno si të preferuar"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Hiq nga të preferuarat"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Kalo në heshtje"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Aktivizo"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Shfaqe si flluskë"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Çaktivizo flluskat"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Shto në ekranin bazë"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"kontrollet e njoftimit"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"opsionet e shtyrjes së njoftimit"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 29e7e1044d61..4127806f9cd3 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Зумирај на целом екрану"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Развуци на цео екран"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Снимак екрана"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Слика је уметнута"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Чување снимка екрана..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Чување снимка екрана..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Снимак екрана је сачуван"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Започни снимање"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Сними пренос гласа"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Приказуј додире"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Додирните да бисте зауставили"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Заустави"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Паузирај"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Настави"</string> @@ -390,6 +392,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Уштеда батерије"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Укључује се по заласку сунца"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"До изласка сунца"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Укључује се у <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"До <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC је онемогућен"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC је омогућен"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index b0d78779daad..3e29fa6e6749 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Zooma för att fylla skärm"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Dra för att fylla skärmen"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Skärmdump"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Bilden har infogats"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Skärmdumpen sparas ..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Skärmdumpen sparas ..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skärmdumpen har sparats"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Börja spela in"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Spela in med mikrofondubbning"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Visa tryck"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Tryck för att stoppa"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Stoppa"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pausa"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Återuppta"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Batterisparläge"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"På från solnedgången"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Till soluppgången"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Aktivera kl. <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Till <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC är inaktiverat"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC är aktiverat"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index ecaefb0c0e4e..9fd4284710d1 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Kuza ili kujaza skrini"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Tanua ili kujaza skrini"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Picha ya skrini"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Picha imewekwa"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Inahifadhi picha ya skrini..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Inahifadhi picha ya skrini..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Imehifadhi picha ya skrini"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Anza Kurekodi"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Rekodi sauti"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Onyesha unapogusa"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Gusa ili ukomeshe"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Acha"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Sitisha"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Endelea"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Kiokoa betri"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Itawashwa machweo"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hadi macheo"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Itawashwa saa <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Hadi saa <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC imezimwa"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC imewashwa"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Rekodi ya Skrini"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Anza kurekodi"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Acha kurekodi"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Telezesha kidole juu ili ubadilishe programu"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Buruta kulia ili ubadilishe programu haraka"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Washa Muhtasari"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Weka mapendeleo"</string> <string name="notification_done" msgid="6215117625922713976">"Nimemaliza"</string> <string name="inline_undo" msgid="9026953267645116526">"Tendua"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Tia alama arifa hii kuwa si mazungumzo"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Tia alama ya kipendwa kwenye mazungumzo haya"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Ondoa alama ya kipendwa kwenye mazungumzo haya"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Komesha mazungumzo"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Ruhusu mazungumzo"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Onyesha kuwa kiputo"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Zima viputo"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Ongeza kwenye skrini ya kwanza"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"vidhibiti vya arifa"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"chaguo za kuahirisha arifa"</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 0bea7d6305d6..55bd472a7889 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"திரையை நிரப்ப அளவை மாற்று"</string> <string name="compat_mode_off" msgid="7682459748279487945">"திரையை நிரப்ப இழு"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"ஸ்கிரீன்ஷாட்"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"படம் செருகப்பட்டது"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"ஸ்க்ரீன் ஷாட்டைச் சேமிக்கிறது…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"ஸ்க்ரீன் ஷாட்டைச் சேமிக்கிறது…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"ஸ்கிரீன்ஷாட் சேமிக்கப்பட்டது"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"ரெக்கார்டிங்கைத் தொடங்கு"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"வாய்ஸ் ஓவரை ரெக்கார்டு செய்"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"தட்டல்களைக் காட்டு"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"நிறுத்த, தட்டவும்"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"நிறுத்து"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"இடைநிறுத்து"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"மீண்டும் தொடங்கு"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"பேட்டரி சேமிப்பான்"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"மாலையில் ஆன் செய்"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"காலை வரை"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g>க்கு ஆன் செய்"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> வரை"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC முடக்கப்பட்டது"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC இயக்கப்பட்டது"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"ஸ்கிரீன் ரெக்கார்டு"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"தொடங்கு"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"நிறுத்து"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ஆப்ஸிற்கு இடையே மாற்றுவதற்கு, மேல்நோக்கி ஸ்வைப் செய்க"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ஆப்ஸை வேகமாக மாற்ற, வலப்புறம் இழுக்கவும்"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"மேலோட்டப் பார்வையை நிலைமாற்று"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"தனிப்பயனாக்கு"</string> <string name="notification_done" msgid="6215117625922713976">"முடிந்தது"</string> <string name="inline_undo" msgid="9026953267645116526">"செயல்தவிர்"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"இந்த அறிவிப்பை உரையாடல் அல்லாததாகக் குறிக்கவும்"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"பிடித்தது"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"பிடித்ததிலிருந்து அகற்று"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"அறிவிப்பைக் காட்டாதே"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"அறிவிப்பைக் காட்டு"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"குமிழாகக் காட்டு"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"குமிழ்களை ஆஃப் செய்"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"முகப்புத் திரையில் சேர்"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"அறிவிப்புக் கட்டுப்பாடுகள்"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"அறிவிப்பை உறக்கநிலையாக்கும் விருப்பங்கள்"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index bb624902beb0..8b9c07ce0264 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"స్క్రీన్కు నింపేలా జూమ్ చేయండి"</string> <string name="compat_mode_off" msgid="7682459748279487945">"స్క్రీన్కు నింపేలా విస్తరించండి"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"స్క్రీన్షాట్"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"చిత్రం చొప్పించబడింది"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"స్క్రీన్షాట్ను సేవ్ చేస్తోంది…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"స్క్రీన్షాట్ను సేవ్ చేస్తోంది…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"స్క్రీన్షాట్ సేవ్ చేయబడింది"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"రికార్డింగ్ను ప్రారంభించు"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"వాయిస్ఓవర్ని రికార్డ్ చేయి"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"నొక్కినవి చూపు"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"ఆపడానికి నొక్కండి"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"ఆపివేయి"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"పాజ్ చేయి"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"కొనసాగించు"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"బ్యాటరీ సేవర్"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"సూర్యాస్తమయానికి"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"సూర్యోదయం వరకు"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> వద్ద"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> వరకు"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC నిలిపివేయబడింది"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ప్రారంభించబడింది"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"స్క్రీన్ రికార్డ్"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"ప్రారంభించు"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"ఆపు"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"యాప్లను మార్చడం కోసం ఎగువకు స్వైప్ చేయండి"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"యాప్లను శీఘ్రంగా స్విచ్ చేయడానికి కుడి వైపుకు లాగండి"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"స్థూలదృష్టిని టోగుల్ చేయి"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"అనుకూలపరచండి"</string> <string name="notification_done" msgid="6215117625922713976">"పూర్తయింది"</string> <string name="inline_undo" msgid="9026953267645116526">"చర్యరద్దు చేయి"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"ఈ నోటిఫికేషన్ను సంభాషణ కానిది అని గుర్తు పెట్టండి"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"ఇష్టమైనది"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"ఇష్టమైనదిగా పెట్టిన గుర్తును తీసివేయి"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"మ్యూట్ చేయి"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"అన్మ్యూట్ చేయి"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"బబుల్లా చూపించు"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"బబుల్లను ఆఫ్ చేయి"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"హోమ్ స్క్రీన్కు జోడించు"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"నోటిఫికేషన్ నియంత్రణలు"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"నోటిఫికేషన్ తాత్కాలిక ఆపివేత ఎంపికలు"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 54c3d7335728..c5e01236912d 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"ขยายจนเต็มหน้าจอ"</string> <string name="compat_mode_off" msgid="7682459748279487945">"ยืดจนเต็มหน้าจอ"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"ภาพหน้าจอ"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"รูปภาพที่แทรก"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"กำลังบันทึกภาพหน้าจอ..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"กำลังบันทึกภาพหน้าจอ..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"บันทึกภาพหน้าจอแล้ว"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"เริ่มต้นการบันทึก"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"บันทึกเสียงบรรยาย"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"แสดงการแตะ"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"แตะเพื่อหยุด"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"หยุด"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"หยุด"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"ทำต่อ"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"โหมดประหยัดแบตเตอรี่"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"เปิดตอนพระอาทิตย์ตก"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"จนพระอาทิตย์ขึ้น"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"เปิดเวลา <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"จนถึง <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC ถูกปิดใช้งาน"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"เปิดใช้งาน NFC แล้ว"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"บันทึกหน้าจอ"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"เริ่ม"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"หยุด"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"เลื่อนขึ้นเพื่อสลับแอป"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"ลากไปทางขวาเพื่อสลับแอปอย่างรวดเร็ว"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"สลับภาพรวม"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"ปรับแต่ง"</string> <string name="notification_done" msgid="6215117625922713976">"เสร็จสิ้น"</string> <string name="inline_undo" msgid="9026953267645116526">"เลิกทำ"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"ทำเครื่องหมายการแจ้งเตือนนี้ว่าไม่ใช่บทสนทนา"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"รายการโปรด"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"นำออกจากรายการโปรด"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"ปิดเสียง"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"เปิดเสียง"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"แสดงเป็นบับเบิล"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"ปิดบับเบิล"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"เพิ่มลงในหน้าจอหลัก"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"ส่วนควบคุมการแจ้งเตือน"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"ตัวเลือกการปิดเสียงแจ้งเตือนชั่วคราว"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index ef812fff45cd..3d343c5b4c0b 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"I-zoom upang punan screen"</string> <string name="compat_mode_off" msgid="7682459748279487945">"I-stretch upang mapuno screen"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Screenshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Inilagay ang larawan"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Sine-save ang screenshot…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Sine-save ang screenshot…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Na-save ang screenshot"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Simulan ang Pag-record"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"I-record ang voiceover"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Ipakita ang mga pag-tap"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"I-tap para ihinto"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Ihinto"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"I-pause"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Ituloy"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Pangtipid sa Baterya"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Mao-on sa sunset"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Hanggang sunrise"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Ma-o-on nang <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Hanggang <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"Naka-disable ang NFC"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"Naka-enable ang NFC"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Pag-record ng Screen"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Magsimula"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ihinto"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Mag-swipe pataas upang lumipat ng app"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"I-drag pakanan para mabilisang magpalipat-lipat ng app"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"I-toggle ang Overview"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"I-customize"</string> <string name="notification_done" msgid="6215117625922713976">"Tapos Na"</string> <string name="inline_undo" msgid="9026953267645116526">"I-undo"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Markahan ang notification na ito bilang hindi pag-uusap"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Gawing Paborito"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"I-unfavorite"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"I-mute"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"I-unmute"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Ipakita bilang bubble"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"I-off ang mga bubble"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Idagdag sa home screen"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"mga kontrol ng notification"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"mga opsyon sa pag-snooze ng notification"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 470606f48981..46e583fb5124 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Yakınlaştır (ekranı kaplasın)"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Genişlet (ekran kapansın)"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Ekran görüntüsü"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Resim eklendi"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Ekran görüntüsü kaydediliyor..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Ekran görüntüsü kaydediliyor..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Ekran görüntüsü kaydedildi"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Kaydı Başlat"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Seslendirme kaydet"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Dokunmaları göster"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Durdurmak için dokunun"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Durdur"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Duraklat"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Devam ettir"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Pil Tasarrufu"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Gün batımı açılacak"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Sabaha kadar"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Açılacağı saat: <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Şu saate kadar: <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC devre dışı"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC etkin"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ekran Kaydı"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Başlat"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Durdur"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Uygulamalar arasında geçiş yapmak için yukarı kaydırın"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Uygulamaları hızlıca değiştirmek için sağa kaydırın"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Genel bakışı aç/kapat"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Özelleştir"</string> <string name="notification_done" msgid="6215117625922713976">"Bitti"</string> <string name="inline_undo" msgid="9026953267645116526">"Geri al"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Bu bildirimi ileti dizisi değil olarak işaretle"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Favori"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Favorilerden kaldır"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Kapat"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Aç"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Balon olarak göster"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Balonları kapat"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Ana ekrana ekle"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"Bildirim kontrolleri"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"bildirim erteleme seçenekleri"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 496206a18668..7baad0df2e55 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Масштабув. на весь екран"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Розтягнути на весь екран"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Знімок екрана"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Вставлене зображення"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Збереження знімка екрана..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Збереження знімка екрана..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Знімок екрана збережено"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Почати запис"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Записувати голосовий супровід"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Показувати дотики"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Натисніть, щоб зупинити"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Зупинити"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Призупинити"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Відновити"</string> @@ -392,15 +394,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Режим енергозбереження"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Увімкнеться ввечері"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"До сходу сонця"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Вмикається о <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"До <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC вимкнено"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC ввімкнено"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Запис екрана"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Почати"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Зупинити"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Проводьте пальцем угору, щоб переходити між додатками"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Перетягуйте праворуч, щоб швидко переходити між додатками"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Увімкнути або вимкнути огляд"</string> @@ -702,22 +703,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Налаштувати"</string> <string name="notification_done" msgid="6215117625922713976">"Готово"</string> <string name="inline_undo" msgid="9026953267645116526">"Відмінити"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Перетворити з ланцюжка повідомлень на сповіщення"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Вибране"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Видалити з вибраного"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Ігнорувати"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Не ігнорувати"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Показувати як спливаюче сповіщення"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Вимкнути спливаючі сповіщення"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Додати на головний екран"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"елементи керування сповіщеннями"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"параметри відкладення сповіщень"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index a6de5095e7e2..97d0a5d1d2a3 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"پوری سکرین پر زوم کریں"</string> <string name="compat_mode_off" msgid="7682459748279487945">"پوری سکرین پر پھیلائیں"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"اسکرین شاٹ"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"تصویر داخل کر دی گئی"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"اسکرین شاٹ محفوظ ہو رہا ہے…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"اسکرین شاٹ محفوظ ہو رہا ہے…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"اسکرین شاٹ محفوظ ہو گیا"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"ریکارڈنگ شروع کریں"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"وائس اوور ریکارڈ کریں"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"تھپتھپاہٹیں دکھائیں"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"روکنے کے لیے تھپتھپائیں"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"روکیں"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"موقوف کریں"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"دوبارہ شروع کریں"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"بیٹری سیور"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"شام کے وقت آن ہوگی"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"طلوع آفتاب تک"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"آن ہوگی بوقت <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> تک"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC غیر فعال ہے"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC فعال ہے"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"اسکرین ریکارڈر کریں"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"آغاز"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"روکیں"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"ایپس سوئچ کرنے کیلئے اوپر سوائپ کریں"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"تیزی سے ایپس کو سوئچ کرنے کے لیے دائیں طرف گھسیٹیں"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"مجموعی جائزہ ٹوگل کریں"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"حسب ضرورت بنائیں"</string> <string name="notification_done" msgid="6215117625922713976">"ہوگیا"</string> <string name="inline_undo" msgid="9026953267645116526">"کالعدم کریں"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"اس اطلاع کو بطور گفتگو نشان زد نہ کریں"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"پسندیدہ"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"پسندیدگی ختم کریں"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"خاموش کریں"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"آواز چلائیں"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"بلبلہ کے بطور دکھائیں"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"بلبلے آف کریں"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"ہوم اسکرین میں شامل کریں"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"اطلاع کے کنٹرولز"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"اطلاع اسنوز کرنے کے اختیارات"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 1eb02f3eafa7..b514f1e9754c 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Ekranga moslashtirish"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Ekran hajmida cho‘zish"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Skrinshot"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Rasm joylandi"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Skrinshot saqlanmoqda…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Skrinshot saqlanmoqda…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinshot saqlandi"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Yozuvni boshlash"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Kadrorti nutqini yozib olish"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Bosishlarni ko‘rsatish"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Toʻxtatish uchun bosing"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"To‘xtatish"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Pauza"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Davom etish"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Quvvat tejash"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Kunbotarda yoqish"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Quyosh chiqqunicha"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"<xliff:g id="TIME">%s</xliff:g> da yoqish"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"<xliff:g id="TIME">%s</xliff:g> gacha"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC o‘chiq"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC yoniq"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index dc3882e53c3d..c4d76187c817 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"T.phóng để lấp đầy m.hình"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Giãn ra để lấp đầy m.hình"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Chụp ảnh màn hình"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Đã chèn hình ảnh"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Đang lưu ảnh chụp màn hình..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Đang lưu ảnh chụp màn hình..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Đã lưu ảnh chụp màn hình"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Bắt đầu ghi"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Ghi phần thuyết minh"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Hiển thị số lần nhấn"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Nhấn để dừng"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Dừng"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Tạm dừng"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Tiếp tục"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Trình tiết kiệm pin"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Bật khi trời tối"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Cho đến khi trời sáng"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Bật vào lúc <xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Cho đến <xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC đã được tắt"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC đã được bật"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"Ghi lại nội dung trên màn hình"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Bắt đầu"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Dừng"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"Vuốt lên để chuyển đổi ứng dụng"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"Kéo sang phải để chuyển đổi nhanh giữa các ứng dụng"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Bật/tắt chế độ xem Tổng quan"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"Tùy chỉnh"</string> <string name="notification_done" msgid="6215117625922713976">"Xong"</string> <string name="inline_undo" msgid="9026953267645116526">"Hoàn tác"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"Đánh dấu thông báo này không phải là cuộc trò chuyện"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"Đánh dấu là yêu thích"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"Bỏ thích"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"Ẩn"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"Hiển thị"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"Hiển thị dưới dạng bong bóng"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"Tắt bong bóng"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"Thêm vào màn hình chính"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"điều khiển thông báo"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"Tùy chọn báo lại thông báo"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index baacdae2da54..56edb64fd798 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"缩放以填满屏幕"</string> <string name="compat_mode_off" msgid="7682459748279487945">"拉伸以填满屏幕"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"屏幕截图"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"已插入图片"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"正在保存屏幕截图..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"正在保存屏幕截图..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"已保存屏幕截图"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"开始录制"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"录制旁白"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"显示点按操作反馈"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"点按即可停止"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"停止"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"暂停"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"继续"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"省电模式"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"在日落时开启"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"在日出时关闭"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"在<xliff:g id="TIME">%s</xliff:g> 开启"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"直到<xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 已停用"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 已启用"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"屏幕录制"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"开始"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"停止"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"向上滑动可切换应用"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"向右拖动可快速切换应用"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切换概览"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"自定义"</string> <string name="notification_done" msgid="6215117625922713976">"完成"</string> <string name="inline_undo" msgid="9026953267645116526">"撤消"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"将此通知标记为非对话"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"收藏"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"取消收藏"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"静音"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"取消静音"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"显示为气泡"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"关闭气泡"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"添加到主屏幕"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g><xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"通知设置"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"通知延后选项"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 0f1696e7b34e..251e23d1526b 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"放大為全螢幕"</string> <string name="compat_mode_off" msgid="7682459748279487945">"放大為全螢幕"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"螢幕截圖"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"已插入圖片"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"正在儲存螢幕擷取畫面..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"正在儲存螢幕擷取畫面..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕擷取畫面已儲存"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"開始錄影"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"錄製畫面外的音效"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"顯示輕按選項"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"輕按即可停止"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"停止"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"暫停"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"繼續"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"省電模式"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"在日落時開啟"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"在日出時關閉"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"於<xliff:g id="TIME">%s</xliff:g>開啟"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"直至<xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 已停用"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 已啟用"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"畫面錄影"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"開始"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"停"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"向上滑動即可切換應用程式"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"向右拖曳即可快速切換應用程式"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切換概覽"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"自訂"</string> <string name="notification_done" msgid="6215117625922713976">"完成"</string> <string name="inline_undo" msgid="9026953267645116526">"復原"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"將此通知標示為非對話"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"我的最愛"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"取消收藏"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"靜音"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"取消靜音"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"以小視窗顯示"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"關閉小視窗"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"加入主畫面"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"通知控制項"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"通知延後選項"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index e8e76ecb6094..a7ec5985a745 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"放大為全螢幕"</string> <string name="compat_mode_off" msgid="7682459748279487945">"放大為全螢幕"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"擷取螢幕畫面"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"已插入圖片"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"正在儲存螢幕截圖…"</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"正在儲存螢幕截圖…"</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕截圖已儲存"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"開始錄製"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"錄製畫面外的音效"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"顯示觸控回應"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"輕觸即可停止"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"停止"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"暫停"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"繼續"</string> @@ -388,15 +390,14 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"節約耗電量"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"於日落時開啟"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"於日出時關閉"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"開啟時間:<xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"關閉時間:<xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC 已停用"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC 已啟用"</string> - <!-- no translation found for quick_settings_screen_record_label (1594046461509776676) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_start (1574725369331638985) --> - <skip /> - <!-- no translation found for quick_settings_screen_record_stop (8087348522976412119) --> - <skip /> + <string name="quick_settings_screen_record_label" msgid="1594046461509776676">"螢幕畫面錄製"</string> + <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"開始"</string> + <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"停止"</string> <string name="recents_swipe_up_onboarding" msgid="2820265886420993995">"向上滑動即可切換應用程式"</string> <string name="recents_quick_scrub_onboarding" msgid="765934300283514912">"向右拖曳即可快速切換應用程式"</string> <string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"切換總覽"</string> @@ -696,22 +697,14 @@ <string name="notification_app_settings" msgid="8963648463858039377">"自訂"</string> <string name="notification_done" msgid="6215117625922713976">"完成"</string> <string name="inline_undo" msgid="9026953267645116526">"復原"</string> - <!-- no translation found for demote (6225813324237153980) --> - <skip /> - <!-- no translation found for notification_conversation_favorite (8252976467488182853) --> - <skip /> - <!-- no translation found for notification_conversation_unfavorite (633301300443356176) --> - <skip /> - <!-- no translation found for notification_conversation_mute (477431709687199671) --> - <skip /> - <!-- no translation found for notification_conversation_unmute (410885000669775294) --> - <skip /> - <!-- no translation found for notification_conversation_bubble (4598142032706190028) --> - <skip /> - <!-- no translation found for notification_conversation_unbubble (2303087159802926401) --> - <skip /> - <!-- no translation found for notification_conversation_home_screen (8347136037958438935) --> - <skip /> + <string name="demote" msgid="6225813324237153980">"將這則通知標示為非對話"</string> + <string name="notification_conversation_favorite" msgid="8252976467488182853">"加入收藏"</string> + <string name="notification_conversation_unfavorite" msgid="633301300443356176">"從收藏中移除"</string> + <string name="notification_conversation_mute" msgid="477431709687199671">"忽略"</string> + <string name="notification_conversation_unmute" msgid="410885000669775294">"取消忽略"</string> + <string name="notification_conversation_bubble" msgid="4598142032706190028">"以泡泡形式顯示"</string> + <string name="notification_conversation_unbubble" msgid="2303087159802926401">"關閉泡泡"</string> + <string name="notification_conversation_home_screen" msgid="8347136037958438935">"新增至主螢幕"</string> <string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string> <string name="notification_menu_gear_description" msgid="6429668976593634862">"通知控制項"</string> <string name="notification_menu_snooze_description" msgid="4740133348901973244">"通知延後選項"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index fa764b575df0..1203b579c52f 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -71,6 +71,7 @@ <string name="compat_mode_on" msgid="4963711187149440884">"Sondeza ukugcwalisa isikrini"</string> <string name="compat_mode_off" msgid="7682459748279487945">"Nweba ukugcwalisa isikrini"</string> <string name="global_action_screenshot" msgid="2760267567509131654">"Isithombe-skrini"</string> + <string name="remote_input_image_insertion_text" msgid="4613177882724332877">"Isithombe sifakiwe"</string> <string name="screenshot_saving_ticker" msgid="6519186952674544916">"Ilondoloz umfanekiso weskrini..."</string> <string name="screenshot_saving_title" msgid="2298349784913287333">"Ilondoloz umfanekiso weskrini..."</string> <string name="screenshot_saved_title" msgid="8893267638659083153">"Isithombe-skrini silondoloziwe"</string> @@ -84,6 +85,7 @@ <string name="screenrecord_start_label" msgid="1539048263178882562">"Qala ukurekhoda"</string> <string name="screenrecord_mic_label" msgid="6134198080740031632">"Rekhoda izwi elingaphezulu"</string> <string name="screenrecord_taps_label" msgid="2518244240225925076">"Bonisa amathebhu"</string> + <string name="screenrecord_stop_text" msgid="6549288689506057686">"Thepha ukuze umise"</string> <string name="screenrecord_stop_label" msgid="72699670052087989">"Misa"</string> <string name="screenrecord_pause_label" msgid="6004054907104549857">"Phumula"</string> <string name="screenrecord_resume_label" msgid="4972223043729555575">"Qalisa kabusha"</string> @@ -388,6 +390,8 @@ <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"Isilondolozi sebhethri"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"Kuvulwe ekushoneni kwelanga"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"Kuze kube sekuphumeni kwelanga"</string> + <string name="quick_settings_dark_mode_secondary_label_on_at" msgid="5128758823486361279">"Kuvulwe ngo-<xliff:g id="TIME">%s</xliff:g>"</string> + <string name="quick_settings_dark_mode_secondary_label_until" msgid="2289774641256492437">"Kuze kube ngu-<xliff:g id="TIME">%s</xliff:g>"</string> <string name="quick_settings_nfc_label" msgid="1054317416221168085">"I-NFC"</string> <string name="quick_settings_nfc_off" msgid="3465000058515424663">"I-NFC ikhutshaziwe"</string> <string name="quick_settings_nfc_on" msgid="1004976611203202230">"I-NFC inikwe amandla"</string> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 1f13f8dc02fe..8d935ecc691d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2549,4 +2549,7 @@ <!-- Quick Controls strings [CHAR LIMIT=30] --> <string name="quick_controls_title">Quick Controls</string> + + <!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] --> + <string name="tile_unavailable">Unvailable</string> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 577e3bbefdad..26ef1d68858e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -27,7 +27,6 @@ oneway interface IOverviewProxy { void onInitialize(in Bundle params) = 12; - /** * @deprecated */ diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 08996c38baf6..386fe531c93d 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -85,6 +85,8 @@ public class QuickStepContract { // The notification panel is expanded and interactive (either locked or unlocked), and the // quick settings is not expanded public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11; + // Winscope tracing is enabled + public static final int SYSUI_STATE_TRACING_ENABLED = 1 << 12; @Retention(RetentionPolicy.SOURCE) @IntDef({SYSUI_STATE_SCREEN_PINNING, @@ -98,7 +100,8 @@ public class QuickStepContract { SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED, SYSUI_STATE_OVERVIEW_DISABLED, SYSUI_STATE_HOME_DISABLED, - SYSUI_STATE_SEARCH_DISABLED + SYSUI_STATE_SEARCH_DISABLED, + SYSUI_STATE_TRACING_ENABLED }) public @interface SystemUiStateFlags {} @@ -117,6 +120,7 @@ public class QuickStepContract { str.add((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0 ? "bouncer_visible" : ""); str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : ""); str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : ""); + str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : ""); return str.toString(); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java new file mode 100644 index 000000000000..557845c34bf2 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/FrameProtoTracer.java @@ -0,0 +1,186 @@ +/* + * 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.shared.tracing; + +import android.os.Trace; +import android.util.Log; +import android.view.Choreographer; + +import com.android.internal.util.TraceBuffer; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; +import java.util.function.Consumer; + +/** + * A proto tracer implementation that can be updated directly (upon state change), or on the next + * scheduled frame. + * + * @param <P> The class type of the proto provider + * @param <S> The proto class type of the encapsulating proto + * @param <T> The proto class type of the individual proto entries in the buffer + * @param <R> The proto class type of the entry root proto in the buffer + */ +public class FrameProtoTracer<P, S extends P, T extends P, R> + implements TraceBuffer.ProtoProvider<P, S, T>, Choreographer.FrameCallback { + + private static final String TAG = "FrameProtoTracer"; + private static final int BUFFER_CAPACITY = 1024 * 1024; + + private final Object mLock = new Object(); + private final TraceBuffer<P, S, T> mBuffer; + private final File mTraceFile; + private final ProtoTraceParams<P, S, T, R> mParams; + private final Choreographer mChoreographer; + private final Queue<T> mPool = new LinkedList<>(); + private final ArrayList<ProtoTraceable<R>> mTraceables = new ArrayList<>(); + private final ArrayList<ProtoTraceable<R>> mTmpTraceables = new ArrayList<>(); + + private volatile boolean mEnabled; + private boolean mFrameScheduled; + + public interface ProtoTraceParams<P, S, T, R> { + File getTraceFile(); + S getEncapsulatingTraceProto(); + T updateBufferProto(T reuseObj, ArrayList<ProtoTraceable<R>> traceables); + byte[] serializeEncapsulatingProto(S encapsulatingProto, Queue<T> buffer); + byte[] getProtoBytes(P proto); + int getProtoSize(P proto); + } + + public FrameProtoTracer(ProtoTraceParams<P, S, T, R> params) { + mParams = params; + mBuffer = new TraceBuffer<>(BUFFER_CAPACITY, this, new Consumer<T>() { + @Override + public void accept(T t) { + onProtoDequeued(t); + } + }); + mTraceFile = params.getTraceFile(); + mChoreographer = Choreographer.getMainThreadInstance(); + } + + @Override + public int getItemSize(P proto) { + return mParams.getProtoSize(proto); + } + + @Override + public byte[] getBytes(P proto) { + return mParams.getProtoBytes(proto); + } + + @Override + public void write(S encapsulatingProto, Queue<T> buffer, OutputStream os) throws IOException { + os.write(mParams.serializeEncapsulatingProto(encapsulatingProto, buffer)); + } + + public void start() { + synchronized (mLock) { + if (mEnabled) { + return; + } + mBuffer.resetBuffer(); + mEnabled = true; + } + logState(); + } + + public void stop() { + synchronized (mLock) { + if (!mEnabled) { + return; + } + mEnabled = false; + } + writeToFile(); + } + + public boolean isEnabled() { + return mEnabled; + } + + public void add(ProtoTraceable<R> traceable) { + synchronized (mLock) { + mTraceables.add(traceable); + } + } + + public void remove(ProtoTraceable<R> traceable) { + synchronized (mLock) { + mTraceables.remove(traceable); + } + } + + public void scheduleFrameUpdate() { + if (!mEnabled || mFrameScheduled) { + return; + } + + // Schedule an update on the next frame + mChoreographer.postFrameCallback(this); + mFrameScheduled = true; + } + + public void update() { + if (!mEnabled) { + return; + } + + logState(); + } + + public float getBufferUsagePct() { + return (float) mBuffer.getBufferSize() / BUFFER_CAPACITY; + } + + @Override + public void doFrame(long frameTimeNanos) { + logState(); + } + + private void onProtoDequeued(T proto) { + mPool.add(proto); + } + + private void logState() { + synchronized (mLock) { + mTmpTraceables.addAll(mTraceables); + } + + mBuffer.add(mParams.updateBufferProto(mPool.poll(), mTmpTraceables)); + mTmpTraceables.clear(); + mFrameScheduled = false; + } + + private void writeToFile() { + try { + Trace.beginSection("ProtoTracer.writeToFile"); + mBuffer.writeTraceToFile(mTraceFile, mParams.getEncapsulatingTraceProto()); + } catch (IOException e) { + Log.e(TAG, "Unable to write buffer to file", e); + } finally { + Trace.endSection(); + } + } +} + + diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java new file mode 100644 index 000000000000..e05b0b074449 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/tracing/ProtoTraceable.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.shared.tracing; + +/** + * @see FrameProtoTracer + */ +public interface ProtoTraceable<T> { + + /** + * NOTE: Implementations should update all fields in this proto. + */ + void writeToProto(T proto); +} diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index aacc2c4f614b..27fe37ef4e69 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -117,14 +117,15 @@ import com.android.systemui.statusbar.policy.SmartReplyConstants; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.wm.DisplayController; import com.android.systemui.wm.DisplayImeController; -import com.android.systemui.wm.DisplayWindowController; import com.android.systemui.wm.SystemWindows; import java.io.FileDescriptor; @@ -323,10 +324,11 @@ public class Dependency { @Inject Lazy<CommandQueue> mCommandQueue; @Inject Lazy<Recents> mRecents; @Inject Lazy<StatusBar> mStatusBar; - @Inject Lazy<DisplayWindowController> mDisplayWindowController; + @Inject Lazy<DisplayController> mDisplayController; @Inject Lazy<SystemWindows> mSystemWindows; @Inject Lazy<DisplayImeController> mDisplayImeController; @Inject Lazy<RecordingController> mRecordingController; + @Inject Lazy<ProtoTracer> mProtoTracer; @Inject public Dependency() { @@ -516,9 +518,10 @@ public class Dependency { mProviders.put(CommandQueue.class, mCommandQueue::get); mProviders.put(Recents.class, mRecents::get); mProviders.put(StatusBar.class, mStatusBar::get); - mProviders.put(DisplayWindowController.class, mDisplayWindowController::get); + mProviders.put(DisplayController.class, mDisplayController::get); mProviders.put(SystemWindows.class, mSystemWindows::get); mProviders.put(DisplayImeController.class, mDisplayImeController::get); + mProviders.put(ProtoTracer.class, mProtoTracer::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 75063e482f08..7b0bd9cadb9b 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -35,6 +35,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.phone.DozeParameters; @@ -153,6 +154,7 @@ public class SystemUIFactory { return new NotificationIconAreaController(context, statusBar, statusBarStateController, wakeUpCoordinator, keyguardBypassController, Dependency.get(NotificationMediaManager.class), + Dependency.get(NotificationListener.class), Dependency.get(DozeParameters.class)); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index ccce85ca74fb..45705b76f09c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -60,6 +60,14 @@ class Bubble { private long mLastUpdated; private long mLastAccessed; + private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; + + /** Whether the bubble should show a dot for the notification indicating updated content. */ + private boolean mShowBubbleUpdateDot = true; + + /** Whether flyout text should be suppressed, regardless of any other flags or state. */ + private boolean mSuppressFlyout; + // Items that are typically loaded later private String mAppName; private ShortcutInfo mShortcutInfo; @@ -71,20 +79,6 @@ class Bubble { private boolean mInflateSynchronously; /** - * Whether this notification should be shown in the shade when it is also displayed as a bubble. - * - * <p>When a notification is a bubble we don't show it in the shade once the bubble has been - * expanded</p> - */ - private boolean mShowInShadeWhenBubble = true; - - /** Whether the bubble should show a dot for the notification indicating updated content. */ - private boolean mShowBubbleUpdateDot = true; - - /** Whether flyout text should be suppressed, regardless of any other flags or state. */ - private boolean mSuppressFlyout; - - /** * Presentational info about the flyout. */ public static class FlyoutMessage { @@ -106,11 +100,13 @@ class Bubble { /** Used in tests when no UI is required. */ @VisibleForTesting(visibility = PRIVATE) - Bubble(NotificationEntry e) { + Bubble(NotificationEntry e, + BubbleController.NotificationSuppressionChangedListener listener) { mEntry = e; mKey = e.getKey(); mLastUpdated = e.getSbn().getPostTime(); mGroupId = groupId(e); + mSuppressionListener = listener; } public String getKey() { @@ -278,7 +274,7 @@ class Bubble { */ void markAsAccessedAt(long lastAccessedMillis) { mLastAccessed = lastAccessedMillis; - setShowInShade(false); + setSuppressNotification(true); setShowDot(false /* show */, true /* animate */); } @@ -290,20 +286,30 @@ class Bubble { } /** - * Whether this notification should be shown in the shade when it is also displayed as a - * bubble. + * Whether this notification should be shown in the shade. */ boolean showInShade() { - return !mEntry.isRowDismissed() && !shouldSuppressNotification() - && (!mEntry.isClearable() || mShowInShadeWhenBubble); + return !shouldSuppressNotification() || !mEntry.isClearable(); } /** - * Sets whether this notification should be shown in the shade when it is also displayed as a - * bubble. + * Sets whether this notification should be suppressed in the shade. */ - void setShowInShade(boolean showInShade) { - mShowInShadeWhenBubble = showInShade; + void setSuppressNotification(boolean suppressNotification) { + boolean prevShowInShade = showInShade(); + + Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); + int flags = data.getFlags(); + if (suppressNotification) { + flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + } else { + flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + } + data.setFlags(flags); + + if (showInShade() != prevShowInShade && mSuppressionListener != null) { + mSuppressionListener.onBubbleNotificationSuppressionChange(this); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 8c9946fcfc7b..ac06f95cadf4 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -190,6 +190,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private boolean mInflateSynchronously; + // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline + private final List<NotifCallback> mCallbacks = new ArrayList<>(); + /** * Listener to be notified when some states of the bubbles change. */ @@ -224,6 +227,45 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** + * Listener to be notified when a bubbles' notification suppression state changes. + */ + public interface NotificationSuppressionChangedListener { + /** + * Called when the notification suppression state of a bubble changes. + */ + void onBubbleNotificationSuppressionChange(Bubble bubble); + } + + /** + * Callback for when the BubbleController wants to interact with the notification pipeline to: + * - Remove a previously bubbled notification + * - Update the notification shade since bubbled notification should/shouldn't be showing + */ + public interface NotifCallback { + /** + * Called when the BubbleController wants to remove an entry that it was previously hiding + * from the shade. See {@link BubbleController#isBubbleNotificationSuppressedFromShade}. + */ + void removeNotification(NotificationEntry entry); + + /** + * Called when a bubbled notification has changed whether it should be + * filtered from the shade. + */ + void invalidateNotificationFilter(String reason); + + /** + * Called on a bubbled entry that has been removed when there are no longer + * bubbled entries in its group. + * + * Checks whether its group has any other (non-bubbled) children. If it doesn't, + * removes all remnants of the group's summary from the notification pipeline. + * TODO: (b/145659174) Only old pipeline needs this - delete post-migration. + */ + void maybeCancelSummary(NotificationEntry entry); + } + + /** * Listens for the current state of the status bar and updates the visibility state * of bubbles as needed. */ @@ -303,31 +345,26 @@ public class BubbleController implements ConfigurationController.ConfigurationLi configurationController.addCallback(this /* configurationListener */); + mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered); mBubbleData = data; mBubbleData.setListener(mBubbleDataListener); - mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered); + mBubbleData.setSuppressionChangedListener(new NotificationSuppressionChangedListener() { + @Override + public void onBubbleNotificationSuppressionChange(Bubble bubble) { + // Make sure NoMan knows it's not showing in the shade anymore so anyone querying it + // can tell. + try { + mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(), + !bubble.showInShade()); + } catch (RemoteException e) { + // Bad things have happened + } + } + }); mNotificationEntryManager = entryManager; - mNotificationEntryManager.addNotificationEntryListener(mEntryListener); - mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor); mNotificationGroupManager = groupManager; - mNotificationGroupManager.addOnGroupChangeListener( - new NotificationGroupManager.OnGroupChangeListener() { - @Override - public void onGroupSuppressionChanged( - NotificationGroupManager.NotificationGroup group, - boolean suppressed) { - // More notifications could be added causing summary to no longer - // be suppressed -- in this case need to remove the key. - final String groupKey = group.summary != null - ? group.summary.getSbn().getGroupKey() - : null; - if (!suppressed && groupKey != null - && mBubbleData.isSummarySuppressed(groupKey)) { - mBubbleData.removeSuppressedSummary(groupKey); - } - } - }); + setupNEM(); mNotificationShadeWindowController = notificationShadeWindowController; mStatusBarStateListener = new StatusBarStateListener(); @@ -367,6 +404,104 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** + * See {@link NotifCallback}. + */ + public void addNotifCallback(NotifCallback callback) { + mCallbacks.add(callback); + } + + private void setupNEM() { + mNotificationEntryManager.addNotificationEntryListener( + new NotificationEntryListener() { + @Override + public void onNotificationAdded(NotificationEntry entry) { + onEntryAdded(entry); + } + + @Override + public void onPreEntryUpdated(NotificationEntry entry) { + onEntryUpdated(entry); + } + + @Override + public void onNotificationRankingUpdated(RankingMap rankingMap) { + onRankingUpdated(rankingMap); + } + }); + + mNotificationEntryManager.setNotificationRemoveInterceptor( + new NotificationRemoveInterceptor() { + @Override + public boolean onNotificationRemoveRequested(String key, int reason) { + NotificationEntry entry = + mNotificationEntryManager.getActiveNotificationUnfiltered(key); + return shouldInterceptDismissal(entry, reason); + } + }); + + mNotificationGroupManager.addOnGroupChangeListener( + new NotificationGroupManager.OnGroupChangeListener() { + @Override + public void onGroupSuppressionChanged( + NotificationGroupManager.NotificationGroup group, + boolean suppressed) { + // More notifications could be added causing summary to no longer + // be suppressed -- in this case need to remove the key. + final String groupKey = group.summary != null + ? group.summary.getSbn().getGroupKey() + : null; + if (!suppressed && groupKey != null + && mBubbleData.isSummarySuppressed(groupKey)) { + mBubbleData.removeSuppressedSummary(groupKey); + } + } + }); + + addNotifCallback(new NotifCallback() { + @Override + public void removeNotification(NotificationEntry entry) { + mNotificationEntryManager.performRemoveNotification(entry.getSbn(), + UNDEFINED_DISMISS_REASON); + } + + @Override + public void invalidateNotificationFilter(String reason) { + mNotificationEntryManager.updateNotifications(reason); + } + + @Override + 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(); + if (mBubbleData.isSummarySuppressed(groupKey)) { + mBubbleData.removeSuppressedSummary(entry.getSbn().getGroupKey()); + + final NotificationEntry summary = + mNotificationEntryManager.getActiveNotificationUnfiltered( + mBubbleData.getSummaryKey(groupKey)); + mNotificationEntryManager.performRemoveNotification(summary.getSbn(), + UNDEFINED_DISMISS_REASON); + } + + // Check if summary should be removed from NoManGroup + NotificationEntry summary = + mNotificationGroupManager.getLogicalGroupSummary(entry.getSbn()); + if (summary != null) { + ArrayList<NotificationEntry> summaryChildren = + mNotificationGroupManager.getLogicalChildren(summary.getSbn()); + boolean isSummaryThisNotif = summary.getKey().equals(entry.getKey()); + if (!isSummaryThisNotif && (summaryChildren == null + || summaryChildren.isEmpty())) { + mNotificationEntryManager.performRemoveNotification(summary.getSbn(), + UNDEFINED_DISMISS_REASON); + } + } + } + }); + } + + /** * Sets whether to perform inflation on the same thread as the caller. This method should only * be used in tests, not in production. */ @@ -538,13 +673,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * * False otherwise. */ - public boolean isBubbleNotificationSuppressedFromShade(String key) { + public boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry) { + String key = entry.getKey(); boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key) && !mBubbleData.getBubbleWithKey(key).showInShade(); - NotificationEntry entry = mNotificationEntryManager.getActiveNotificationUnfiltered(key); - String groupKey = entry != null ? entry.getSbn().getGroupKey() : null; + + String groupKey = entry.getSbn().getGroupKey(); boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey); boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey)); + return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed; } @@ -678,160 +815,46 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } - @SuppressWarnings("FieldCanBeLocal") - private final NotificationRemoveInterceptor mRemoveInterceptor = - new NotificationRemoveInterceptor() { - @Override - public boolean onNotificationRemoveRequested(String key, int reason) { - NotificationEntry entry = - mNotificationEntryManager.getActiveNotificationUnfiltered(key); - 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(); - - if (!inBubbleData && !isSummaryOfBubbles) { - return false; - } - - final boolean isClearAll = reason == REASON_CANCEL_ALL; - final boolean isUserDimiss = reason == REASON_CANCEL || reason == REASON_CLICK; - final boolean isAppCancel = reason == REASON_APP_CANCEL - || reason == REASON_APP_CANCEL_ALL; - final boolean isSummaryCancel = reason == 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); - } - - // 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.setShowInShade(false); - bubble.setShowDot(false /* show */, true /* animate */); - mNotificationEntryManager.updateNotifications( - "BubbleController.onNotificationRemoveRequested"); - 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); - return false; - } - return false; - } - }; - - private boolean handleSummaryRemovalInterception(NotificationEntry summary, - boolean userRemovedNotif) { - String groupKey = summary.getSbn().getGroupKey(); - ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); - - 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.setShowInShade(false); - 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) { - mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(), - summary.getKey()); - // Tell shade to update for the suppression - mNotificationEntryManager.updateNotifications( - "BubbleController.handleSummaryRemovalInterception"); + private void onEntryAdded(NotificationEntry entry) { + boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey()); + boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey()); + boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( + mContext, entry, previouslyUserCreated, userBlocked); + + if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) + && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) { + if (wasAdjusted && !previouslyUserCreated) { + // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated + mUserCreatedBubbles.add(entry.getKey()); } - 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; - } - } - // 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; + updateBubble(entry); } } - @SuppressWarnings("FieldCanBeLocal") - private final NotificationEntryListener mEntryListener = new NotificationEntryListener() { - @Override - public void onNotificationAdded(NotificationEntry entry) { - boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey()); - boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey()); - boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( - mContext, entry, previouslyUserCreated, userBlocked); - - if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry) - && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) { - if (wasAdjusted && !previouslyUserCreated) { - // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated - mUserCreatedBubbles.add(entry.getKey()); - } - updateBubble(entry); - } - } - - @Override - public void onPreEntryUpdated(NotificationEntry entry) { - boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey()); - boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey()); - boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( - mContext, entry, previouslyUserCreated, userBlocked); - - boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry) - && (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); - } else if (shouldBubble) { - if (wasAdjusted && !previouslyUserCreated) { - // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated - mUserCreatedBubbles.add(entry.getKey()); - } - updateBubble(entry); + private void onEntryUpdated(NotificationEntry entry) { + boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey()); + boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey()); + boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments( + mContext, entry, previouslyUserCreated, userBlocked); + + boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry) + && (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); + } else if (shouldBubble) { + if (wasAdjusted && !previouslyUserCreated) { + // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated + mUserCreatedBubbles.add(entry.getKey()); } + updateBubble(entry); } + } - @Override - public void onNotificationRankingUpdated(RankingMap rankingMap) { - // Forward to BubbleData to block any bubbles which should no longer be shown - mBubbleData.notificationRankingUpdated(rankingMap); - } - }; + private void onRankingUpdated(RankingMap rankingMap) { + // Forward to BubbleData to block any bubbles which should no longer be shown + mBubbleData.notificationRankingUpdated(rankingMap); + } @SuppressWarnings("FieldCanBeLocal") private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { @@ -864,9 +887,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (reason != DISMISS_USER_CHANGED) { if (!mBubbleData.hasBubbleWithKey(bubble.getKey()) && !bubble.showInShade()) { - // The bubble is gone & the notification is gone, time to actually remove it - mNotificationEntryManager.performRemoveNotification( - bubble.getEntry().getSbn(), UNDEFINED_DISMISS_REASON); + // 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()); + } } else { // Update the flag for SysUI bubble.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE; @@ -881,32 +906,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } - // Check if removed bubble has an associated suppressed group summary that needs - // to be removed now. final String groupKey = bubble.getEntry().getSbn().getGroupKey(); - if (mBubbleData.isSummarySuppressed(groupKey) - && mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { - // Time to actually remove the summary. - String notifKey = mBubbleData.getSummaryKey(groupKey); - mBubbleData.removeSuppressedSummary(groupKey); - NotificationEntry entry = - mNotificationEntryManager.getActiveNotificationUnfiltered(notifKey); - mNotificationEntryManager.performRemoveNotification( - entry.getSbn(), UNDEFINED_DISMISS_REASON); - } - - // Check if summary should be removed from NoManGroup - NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary( - bubble.getEntry().getSbn()); - if (summary != null) { - ArrayList<NotificationEntry> summaryChildren = - mNotificationGroupManager.getLogicalChildren(summary.getSbn()); - boolean isSummaryThisNotif = summary.getKey().equals( - bubble.getEntry().getKey()); - if (!isSummaryThisNotif - && (summaryChildren == null || summaryChildren.isEmpty())) { - mNotificationEntryManager.performRemoveNotification( - summary.getSbn(), UNDEFINED_DISMISS_REASON); + if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { + // Time to potentially remove the summary + for (NotifCallback cb : mCallbacks) { + cb.maybeCancelSummary(bubble.getEntry()); } } } @@ -935,8 +939,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mStackView.setExpanded(true); } - mNotificationEntryManager.updateNotifications( - "BubbleData.Listener.applyUpdate"); + for (NotifCallback cb : mCallbacks) { + cb.invalidateNotificationFilter("BubbleData.Listener.applyUpdate"); + } updateStack(); if (DEBUG_BUBBLE_CONTROLLER) { @@ -957,6 +962,127 @@ public class BubbleController implements ConfigurationController.ConfigurationLi }; /** + * We intercept notification entries cancelled by the user (i.e. dismissed) when there is an + * active bubble associated with it. We do this so that developers can still cancel it + * (and hence the bubbles associated with it). However, these intercepted notifications + * should then be hidden from the shade since the user has cancelled them, so we update + * {@link Bubble#showInShade}. + * + * The cancellation of summaries with children associated with bubbles are also handled in this + * method. User-cancelled summaries are tracked by {@link BubbleData#addSummaryToSuppress}. + * + * @return true if we want to intercept the dismissal of the entry, else false + */ + public boolean shouldInterceptDismissal(NotificationEntry entry, int dismissReason) { + 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(); + + if (!inBubbleData && !isSummaryOfBubbles) { + 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); + } + + // 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); + return false; + } + return false; + } + + private boolean handleSummaryRemovalInterception(NotificationEntry summary, + boolean userRemovedNotif) { + String groupKey = summary.getSbn().getGroupKey(); + ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + + 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; + } + } + + // 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; + } + } + + /** * Lets any listeners know if bubble state has changed. * Updates the visibility of the bubbles based on current state. * Does not un-bubble, just hides or un-hides. Notifies any @@ -1074,12 +1200,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi @Override public void onSingleTaskDisplayDrawn(int displayId) { - final Bubble expandedBubble = mStackView != null - ? mStackView.getExpandedBubble() - : null; - if (expandedBubble != null && expandedBubble.getDisplayId() == displayId) { - expandedBubble.setContentVisibility(true); + if (mStackView == null) { + return; } + mStackView.showExpandedViewContents(displayId); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 8b687e7114db..673121f92716 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -134,6 +134,9 @@ public class BubbleData { @Nullable private Listener mListener; + @Nullable + private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; + /** * We track groups with summaries that aren't visibly displayed but still kept around because * the bubble(s) associated with the summary still exist. @@ -158,6 +161,11 @@ public class BubbleData { mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow); } + public void setSuppressionChangedListener( + BubbleController.NotificationSuppressionChangedListener listener) { + mSuppressionListener = listener; + } + public boolean hasBubbles() { return !mBubbles.isEmpty(); } @@ -219,7 +227,7 @@ public class BubbleData { return b; } } - bubble = new Bubble(entry); + bubble = new Bubble(entry, mSuppressionListener); mPendingBubbles.add(bubble); } else { bubble.setEntry(entry); @@ -258,11 +266,13 @@ public class BubbleData { } else if (mSelectedBubble == null) { setSelectedBubbleInternal(bubble); } + boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble; - bubble.setShowInShade(!isBubbleExpandedAndSelected && showInShade); + boolean suppress = isBubbleExpandedAndSelected || !showInShade || !bubble.showInShade(); + bubble.setSuppressNotification(suppress); bubble.setShowDot(!isBubbleExpandedAndSelected /* show */, true /* animate */); - dispatchPendingChanges(); + dispatchPendingChanges(); } public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index fe4c91509057..685bb9490a9a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -409,8 +409,12 @@ public class BubbleStackView extends FrameLayout { .setStiffness(SpringForce.STIFFNESS_LOW) .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> { - if (mIsExpanded && mExpandedBubble != null) { - mExpandedBubble.getExpandedView().updateView(); + if (mIsExpanded) { + if (mExpandedBubble == null) { + mOverflowExpandedView.updateView(); + } else { + mExpandedBubble.getExpandedView().updateView(); + } } }); @@ -525,7 +529,7 @@ public class BubbleStackView extends FrameLayout { mInflater.inflate(R.layout.bubble_overflow_button, this); mOverflowBtn = findViewById(R.id.bubble_overflow_button); mOverflowBtn.setOnClickListener(v -> { - showOverflow(); + setSelectedBubble(null); }); TypedArray ta = mContext.obtainStyledAttributes( @@ -540,26 +544,13 @@ public class BubbleStackView extends FrameLayout { mOverflowBtn.setVisibility(GONE); } - void showOverflow() { - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "Show overflow."); + void showExpandedViewContents(int displayId) { + if (mOverflowExpandedView.getVirtualDisplayId() == displayId) { + mOverflowExpandedView.setContentVisibility(true); + } else if (mExpandedBubble != null + && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) { + mExpandedBubble.setContentVisibility(true); } - mExpandedViewContainer.setAlpha(0.0f); - mSurfaceSynchronizer.syncSurfaceAndRun(() -> { - if (mExpandedBubble != null) { - mExpandedBubble.setContentVisibility(false); - mExpandedBubble = null; - } - mExpandedViewContainer.removeAllViews(); - if (mIsExpanded) { - mExpandedViewContainer.addView(mOverflowExpandedView); - mOverflowExpandedView.populateExpandedView(); - mExpandedViewContainer.setVisibility(VISIBLE); - mExpandedViewContainer.setAlpha(1.0f); - mOverflowExpandedView.setContentVisibility(true); - } - requestUpdate(); - }); } private void setUpFlyout() { @@ -883,7 +874,9 @@ public class BubbleStackView extends FrameLayout { // expanded view becomes visible on the screen. See b/126856255 mExpandedViewContainer.setAlpha(0.0f); mSurfaceSynchronizer.syncSurfaceAndRun(() -> { - if (previouslySelected != null) { + if (previouslySelected == null) { + mOverflowExpandedView.setContentVisibility(false); + } else { previouslySelected.setContentVisibility(false); } updateExpandedBubble(); @@ -1055,7 +1048,9 @@ public class BubbleStackView extends FrameLayout { () -> { mBubbleContainer.setActiveController(mStackAnimationController); afterExpandedViewAnimation(); - if (previouslySelected != null) { + if (previouslySelected == null) { + mOverflowExpandedView.setContentVisibility(false); + } else { previouslySelected.setContentVisibility(false); } }); @@ -1625,10 +1620,14 @@ public class BubbleStackView extends FrameLayout { Log.d(TAG, "updateExpandedBubble()"); } mExpandedViewContainer.removeAllViews(); - if (mExpandedBubble != null && mIsExpanded) { - mExpandedViewContainer.addView(mExpandedBubble.getExpandedView()); - mExpandedBubble.getExpandedView().populateExpandedView(); - mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); + if (mIsExpanded) { + BubbleExpandedView bev = mOverflowExpandedView; + if (mExpandedBubble != null) { + bev = mExpandedBubble.getExpandedView(); + } + mExpandedViewContainer.addView(bev); + bev.populateExpandedView(); + mExpandedViewContainer.setVisibility(VISIBLE); mExpandedViewContainer.setAlpha(1.0f); } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 36a845002deb..f793b3df92a4 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -27,6 +27,7 @@ import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.DumpController; import com.android.systemui.assist.AssistModule; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.log.dagger.LogModule; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.Recents; @@ -59,9 +60,12 @@ import dagger.Provides; * A dagger module for injecting components of System UI that are not overridden by the System UI * implementation. */ -@Module(includes = {AssistModule.class, - ConcurrencyModule.class, - PeopleHubModule.class}, +@Module(includes = { + AssistModule.class, + ConcurrencyModule.class, + LogModule.class, + PeopleHubModule.class, + }, subcomponents = {StatusBarComponent.class, NotificationRowComponent.class}) public abstract class SystemUIModule { diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt new file mode 100644 index 000000000000..18c7baec1f74 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt @@ -0,0 +1,213 @@ +/* + * 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.log + +import android.util.Log +import com.android.systemui.DumpController +import com.android.systemui.Dumpable +import com.android.systemui.log.dagger.LogModule +import java.text.SimpleDateFormat +import java.util.ArrayDeque +import java.util.Locale + +/** + * A simple ring buffer of recyclable log messages + * + * The goal of this class is to enable logging that is both extremely chatty and extremely + * lightweight. If done properly, logging a message will not result in any heap allocations or + * string generation. Messages are only converted to strings if the log is actually dumped (usually + * as the result of taking a bug report). + * + * You can dump the entire buffer at any time by running: + * + * ``` + * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService \ + * dependency DumpController <bufferName> + * ``` + * + * where `bufferName` is the (case-sensitive) [name] passed to the constructor. + * + * By default, only messages of WARN level or higher are echoed to logcat, but this can be adjusted + * locally (usually for debugging purposes). + * + * To enable logcat echoing for an entire buffer: + * + * ``` + * $ adb shell settings put global systemui/buffer/<bufferName> <level> + * ``` + * + * To enable logcat echoing for a specific tag: + * + * ``` + * $ adb shell settings put global systemui/tag/<tag> <level> + * ``` + * + * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or + * the first letter of any of the previous. + * + * Buffers are provided by [LogModule]. + * + * @param name The name of this buffer + * @param maxLogs The maximum number of messages to keep in memory at any one time, including the + * unused pool. + * @param poolSize The maximum amount that the size of the buffer is allowed to flex in response to + * sequential calls to [document] that aren't immediately followed by a matching call to [push]. + */ +class LogBuffer( + private val name: String, + private val maxLogs: Int, + private val poolSize: Int, + private val logcatEchoTracker: LogcatEchoTracker +) { + private val buffer: ArrayDeque<LogMessageImpl> = ArrayDeque() + + fun attach(dumpController: DumpController) { + dumpController.registerDumpable(name, onDump) + } + + /** + * Logs a message to the log buffer + * + * May also log the message to logcat if echoing is enabled for this buffer or tag. + * + * The actual string of the log message is not constructed until it is needed. To accomplish + * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is + * obtained and is passed to the [initializer]. The initializer stores any relevant data on the + * message's fields. The message is then inserted into the buffer where it waits until it is + * either pushed out by newer messages or it needs to printed. If and when this latter moment + * occurs, the [printer] function is called on the message. It reads whatever data the + * initializer stored and converts it to a human-readable log message. + * + * @param tag A string of at most 23 characters, used for grouping logs into categories or + * subjects. If this message is echoed to logcat, this will be the tag that is used. + * @param level Which level to log the message at, both to the buffer and to logcat if it's + * echoed. In general, a module should split most of its logs into either INFO or DEBUG level. + * INFO level should be reserved for information that other parts of the system might care + * about, leaving the specifics of code's day-to-day operations to DEBUG. + * @param initializer A function that will be called immediately to store relevant data on the + * log message. The value of `this` will be the LogMessage to be initialized. + * @param printer A function that will be called if and when the message needs to be dumped to + * logcat or a bug report. It should read the data stored by the initializer and convert it to + * a human-readable string. The value of `this` will be the LogMessage to be printed. + * **IMPORTANT:** The printer should ONLY ever reference fields on the LogMessage and NEVER any + * variables in its enclosing scope. Otherwise, the runtime will need to allocate a new instance + * of the printer for each call, thwarting our attempts at avoiding any sort of allocation. + */ + inline fun log( + tag: String, + level: LogLevel, + initializer: LogMessage.() -> Unit, + noinline printer: LogMessage.() -> String + ) { + val message = obtain(tag, level, printer) + initializer(message) + push(message) + } + + /** + * Same as [log], but doesn't push the message to the buffer. Useful if you need to supply a + * "reason" for doing something (the thing you supply the reason to will presumably call [push] + * on that message at some point). + */ + inline fun document( + tag: String, + level: LogLevel, + initializer: LogMessage.() -> Unit, + noinline printer: LogMessage.() -> String + ): LogMessage { + val message = obtain(tag, level, printer) + initializer(message) + return message + } + + /** + * Obtains an instance of [LogMessageImpl], usually from the object pool. If the pool has been + * exhausted, creates a new instance. + * + * In general, you should call [log] or [document] instead of this method. + */ + fun obtain( + tag: String, + level: LogLevel, + printer: (LogMessage) -> String + ): LogMessageImpl { + val message = synchronized(buffer) { + if (buffer.size > maxLogs - poolSize) { + buffer.removeFirst() + } else { + LogMessageImpl.create() + } + } + message.reset(tag, level, System.currentTimeMillis(), printer) + return message + } + + /** + * Pushes a message into buffer, possibly evicting an older message if the buffer is full. + */ + fun push(message: LogMessage) { + synchronized(buffer) { + if (buffer.size == maxLogs) { + Log.e(TAG, "LogBuffer $name has exceeded its pool size") + buffer.removeFirst() + } + buffer.add(message as LogMessageImpl) + if (logcatEchoTracker.isBufferLoggable(name, message.level) || + logcatEchoTracker.isTagLoggable(message.tag, message.level)) { + echoToLogcat(message) + } + } + } + + /** Converts the entire buffer to a newline-delimited string */ + fun dump(): String { + synchronized(buffer) { + val sb = StringBuilder() + for (message in buffer) { + dumpMessage(message, sb) + } + return sb.toString() + } + } + + private fun dumpMessage(message: LogMessage, sb: StringBuilder) { + sb.append(DATE_FORMAT.format(message.timestamp)) + .append(" ").append(message.level) + .append(" ").append(message.tag) + .append(" ").append(message.printer(message)) + .append("\n") + } + + private fun echoToLogcat(message: LogMessage) { + val strMessage = message.printer(message) + when (message.level) { + LogLevel.VERBOSE -> Log.v(message.tag, strMessage) + LogLevel.DEBUG -> Log.d(message.tag, strMessage) + LogLevel.INFO -> Log.i(message.tag, strMessage) + LogLevel.WARNING -> Log.w(message.tag, strMessage) + LogLevel.ERROR -> Log.e(message.tag, strMessage) + LogLevel.WTF -> Log.wtf(message.tag, strMessage) + } + } + + private val onDump = Dumpable { _, pw, _ -> + pw.println(dump()) + } +} + +private const val TAG = "LogBuffer" +private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.S", Locale.US) diff --git a/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt new file mode 100644 index 000000000000..7b9af0f91200 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogLevel.kt @@ -0,0 +1,31 @@ +/* + * 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.log + +import android.util.Log + +/** + * Enum version of @Log.Level + */ +enum class LogLevel(@Log.Level val nativeLevel: Int) { + VERBOSE(Log.VERBOSE), + DEBUG(Log.DEBUG), + INFO(Log.INFO), + WARNING(Log.WARN), + ERROR(Log.ERROR), + WTF(Log.ASSERT) +} diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt new file mode 100644 index 000000000000..d971ac58fb0b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt @@ -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.log + +/** + * Generic data class for storing messages logged to a [LogBuffer] + * + * Each LogMessage has a few standard fields ([level], [tag], and [timestamp]). The rest are generic + * data slots that may or may not be used, depending on the nature of the specific message being + * logged. + * + * When a message is logged, the code doing the logging stores data in one or more of the generic + * fields ([str1], [int1], etc). When it comes time to dump the message to logcat/bugreport/etc, the + * [printer] function reads the data stored in the generic fields and converts that to a human- + * readable string. Thus, for every log type there must be a specialized initializer function that + * stores data specific to that log type and a specialized printer function that prints that data. + * + * See [LogBuffer.log] for more information. + */ +interface LogMessage { + val level: LogLevel + val tag: String + val timestamp: Long + val printer: LogMessage.() -> String + + var str1: String? + var str2: String? + var str3: String? + var int1: Int + var int2: Int + var long1: Long + var double1: Double +} diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt new file mode 100644 index 000000000000..32334bc382e1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogMessageImpl.kt @@ -0,0 +1,74 @@ +/* + * 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.log + +/** + * Recyclable implementation of [LogMessage]. + */ +data class LogMessageImpl( + override var level: LogLevel, + override var tag: String, + override var timestamp: Long, + override var printer: LogMessage.() -> String, + override var str1: String?, + override var str2: String?, + override var str3: String?, + override var int1: Int, + override var int2: Int, + override var long1: Long, + override var double1: Double +) : LogMessage { + + fun reset( + tag: String, + level: LogLevel, + timestamp: Long, + renderer: LogMessage.() -> String + ) { + this.level = level + this.tag = tag + this.timestamp = timestamp + this.printer = renderer + str1 = null + str2 = null + str3 = null + int1 = 0 + int2 = 0 + long1 = 0 + double1 = 0.0 + } + + companion object Factory { + fun create(): LogMessageImpl { + return LogMessageImpl( + LogLevel.DEBUG, + DEFAULT_TAG, + 0, + DEFAULT_RENDERER, + null, + null, + null, + 0, + 0, + 0, + 0.0) + } + } +} + +private const val DEFAULT_TAG = "UnknownTag" +private val DEFAULT_RENDERER: LogMessage.() -> String = { "Unknown message: $this" } diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt new file mode 100644 index 000000000000..3022f4b42a42 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt @@ -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.log + +/** + * Keeps track of which [LogBuffer] messages should also appear in logcat. + */ +interface LogcatEchoTracker { + /** + * Whether [bufferName] should echo messages of [level] or higher to logcat. + */ + fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean + + /** + * Whether [tagName] should echo messages of [level] or higher to logcat. + */ + fun isTagLoggable(tagName: String, level: LogLevel): Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt new file mode 100644 index 000000000000..23942e1d6e3c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt @@ -0,0 +1,133 @@ +/* + * 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.log + +import android.content.ContentResolver +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.Looper +import android.provider.Settings + +/** + * Version of [LogcatEchoTracker] for debuggable builds + * + * The log level of individual buffers or tags can be controlled via global settings: + * + * ``` + * # Echo any message to <bufferName> of <level> or higher + * $ adb shell settings put global systemui/buffer/<bufferName> <level> + * + * # Echo any message of <tag> and of <level> or higher + * $ adb shell settings put global systemui/tag/<tag> <level> + * ``` + */ +class LogcatEchoTrackerDebug private constructor( + private val contentResolver: ContentResolver +) : LogcatEchoTracker { + private val cachedBufferLevels: MutableMap<String, LogLevel> = mutableMapOf() + private val cachedTagLevels: MutableMap<String, LogLevel> = mutableMapOf() + + companion object Factory { + @JvmStatic + fun create( + contentResolver: ContentResolver, + mainLooper: Looper + ): LogcatEchoTrackerDebug { + val tracker = LogcatEchoTrackerDebug(contentResolver) + tracker.attach(mainLooper) + return tracker + } + } + + private fun attach(mainLooper: Looper) { + contentResolver.registerContentObserver( + Settings.Global.getUriFor(BUFFER_PATH), + true, + object : ContentObserver(Handler(mainLooper)) { + override fun onChange(selfChange: Boolean, uri: Uri) { + super.onChange(selfChange, uri) + cachedBufferLevels.clear() + } + }) + + contentResolver.registerContentObserver( + Settings.Global.getUriFor(TAG_PATH), + true, + object : ContentObserver(Handler(mainLooper)) { + override fun onChange(selfChange: Boolean, uri: Uri) { + super.onChange(selfChange, uri) + cachedTagLevels.clear() + } + }) + } + + /** + * Whether [bufferName] should echo messages of [level] or higher to logcat. + */ + @Synchronized + override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean { + return level.ordinal >= getLogLevel(bufferName, BUFFER_PATH, cachedBufferLevels).ordinal + } + + /** + * Whether [tagName] should echo messages of [level] or higher to logcat. + */ + @Synchronized + override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { + return level >= getLogLevel(tagName, TAG_PATH, cachedTagLevels) + } + + private fun getLogLevel( + name: String, + path: String, + cache: MutableMap<String, LogLevel> + ): LogLevel { + return cache[name] ?: readSetting("$path/$name").also { cache[name] = it } + } + + private fun readSetting(path: String): LogLevel { + return try { + parseProp(Settings.Global.getString(contentResolver, path)) + } catch (_: Settings.SettingNotFoundException) { + DEFAULT_LEVEL + } + } + + private fun parseProp(propValue: String?): LogLevel { + return when (propValue?.toLowerCase()) { + "verbose" -> LogLevel.VERBOSE + "v" -> LogLevel.VERBOSE + "debug" -> LogLevel.DEBUG + "d" -> LogLevel.DEBUG + "info" -> LogLevel.INFO + "i" -> LogLevel.INFO + "warning" -> LogLevel.WARNING + "warn" -> LogLevel.WARNING + "w" -> LogLevel.WARNING + "error" -> LogLevel.ERROR + "e" -> LogLevel.ERROR + "assert" -> LogLevel.WTF + "wtf" -> LogLevel.WTF + else -> DEFAULT_LEVEL + } + } +} + +private val DEFAULT_LEVEL = LogLevel.WARNING +private const val BUFFER_PATH = "systemui/buffer" +private const val TAG_PATH = "systemui/tag" diff --git a/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt new file mode 100644 index 000000000000..394f624a3e58 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt @@ -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.log + +/** + * Production version of [LogcatEchoTracker] that isn't configurable. + */ +class LogcatEchoTrackerProd : LogcatEchoTracker { + override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean { + return level >= LogLevel.WARNING + } + + override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { + return level >= LogLevel.WARNING + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java new file mode 100644 index 000000000000..7c5f4025117f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DozeLog.java @@ -0,0 +1,33 @@ +/* + * 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.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for dozing-related messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface DozeLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java new file mode 100644 index 000000000000..b1990beb9f57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.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.log.dagger; + +import android.content.ContentResolver; +import android.os.Build; +import android.os.Looper; + +import com.android.systemui.DumpController; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.LogcatEchoTracker; +import com.android.systemui.log.LogcatEchoTrackerDebug; +import com.android.systemui.log.LogcatEchoTrackerProd; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * Dagger module for providing instances of {@link LogBuffer}. + */ +@Module +public class LogModule { + /** Provides a logging buffer for doze-related logs. */ + @Provides + @Singleton + @DozeLog + public static LogBuffer provideDozeLogBuffer( + LogcatEchoTrackerDebug bufferFilter, + DumpController dumpController) { + LogBuffer buffer = new LogBuffer("DozeLog", 100, 10, bufferFilter); + buffer.attach(dumpController); + return buffer; + } + + /** Provides a logging buffer for all logs related to the data layer of notifications. */ + @Provides + @Singleton + @NotificationLog + public static LogBuffer provideNotificationsLogBuffer( + LogcatEchoTracker bufferFilter, + DumpController dumpController) { + LogBuffer buffer = new LogBuffer("NotifLog2", 1000, 10, bufferFilter); + buffer.attach(dumpController); + return buffer; + } + + /** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */ + @Provides + @Singleton + public static LogcatEchoTracker provideLogcatEchoTracker( + ContentResolver contentResolver, + @Main Looper looper) { + if (Build.IS_DEBUGGABLE) { + return LogcatEchoTrackerDebug.create(contentResolver, looper); + } else { + return new LogcatEchoTrackerProd(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java new file mode 100644 index 000000000000..a0b686487bec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationLog.java @@ -0,0 +1,33 @@ +/* + * 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.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for notification-related messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface NotificationLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java index d1d9b3de7e69..92aa02050d28 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java @@ -20,13 +20,13 @@ import android.content.Context; import android.content.res.Configuration; import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.wm.DisplayWindowController; +import com.android.systemui.wm.DisplayController; import java.io.PrintWriter; public interface BasePipManager { void initialize(Context context, BroadcastDispatcher broadcastDispatcher, - DisplayWindowController displayWindowController); + DisplayController displayController); void showPictureInPictureMenu(); default void expandPip() {} default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {} diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 8e34a90e7b51..6f03f18ef64b 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -261,8 +261,6 @@ public class PipBoundsHandler { mPinnedStackController.startAnimation(destinationBounds, sourceRectHint, -1 /* animationDuration */); mLastDestinationBounds.set(destinationBounds); - mPinnedStackController.reportBounds(defaultBounds, - getMovementBounds(defaultBounds)); } catch (RemoteException e) { Log.e(TAG, "Failed to start PiP animation from SysUI", e); } @@ -317,7 +315,6 @@ public class PipBoundsHandler { outBounds.set(postChangeStackBounds); mLastDestinationBounds.set(outBounds); mPinnedStackController.resetBoundsAnimation(outBounds); - mPinnedStackController.reportBounds(outBounds, getMovementBounds(outBounds)); t.setBounds(pinnedStackInfo.stackToken, outBounds); } catch (RemoteException e) { Log.e(TAG, "Failed to resize PiP on display rotation", e); diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java index 29de90bb2824..cecdc9cbb4f3 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java @@ -28,7 +28,7 @@ import android.os.UserManager; import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.wm.DisplayWindowController; +import com.android.systemui.wm.DisplayController; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -45,17 +45,17 @@ public class PipUI extends SystemUI implements CommandQueue.Callbacks { private final CommandQueue mCommandQueue; private BasePipManager mPipManager; private final BroadcastDispatcher mBroadcastDispatcher; - private final DisplayWindowController mDisplayWindowController; + private final DisplayController mDisplayController; private boolean mSupportsPip; @Inject public PipUI(Context context, CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher, - DisplayWindowController displayWindowController) { + DisplayController displayController) { super(context); mBroadcastDispatcher = broadcastDispatcher; mCommandQueue = commandQueue; - mDisplayWindowController = displayWindowController; + mDisplayController = displayController; } @Override @@ -75,7 +75,7 @@ public class PipUI extends SystemUI implements CommandQueue.Callbacks { mPipManager = pm.hasSystemFeature(FEATURE_LEANBACK_ONLY) ? com.android.systemui.pip.tv.PipManager.getInstance() : com.android.systemui.pip.phone.PipManager.getInstance(); - mPipManager.initialize(mContext, mBroadcastDispatcher, mDisplayWindowController); + mPipManager.initialize(mContext, mBroadcastDispatcher, mDisplayController); mCommandQueue.addCallback(this); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index e48a23f4cd2c..239ef3638ff6 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -46,7 +46,8 @@ import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.WindowManagerWrapper; -import com.android.systemui.wm.DisplayWindowController; +import com.android.systemui.wm.DisplayChangeController; +import com.android.systemui.wm.DisplayController; import java.io.PrintWriter; @@ -79,7 +80,7 @@ public class PipManager implements BasePipManager { /** * Handler for display rotation changes. */ - private final DisplayWindowController.OnDisplayWindowRotationController mRotationController = ( + private final DisplayChangeController.OnDisplayChangingListener mRotationController = ( int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> { final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mTmpNormalBounds, displayId, fromRotation, toRotation, t); @@ -230,7 +231,7 @@ public class PipManager implements BasePipManager { * Initializes {@link PipManager}. */ public void initialize(Context context, BroadcastDispatcher broadcastDispatcher, - DisplayWindowController displayWindowController) { + DisplayController displayController) { mContext = context; mActivityManager = ActivityManager.getService(); mActivityTaskManager = ActivityTaskManager.getService(); @@ -251,7 +252,7 @@ public class PipManager implements BasePipManager { mMenuController, mInputConsumerController, mPipBoundsHandler); mAppOpsListener = new PipAppOpsListener(context, mActivityManager, mTouchHandler.getMotionHelper()); - displayWindowController.addRotationController(mRotationController); + displayController.addDisplayChangingController(mRotationController); // If SystemUI restart, and it already existed a pinned stack, // register the pip input consumer to ensure touch can send to it. diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index 1d92375a7541..7532f9f11296 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -55,7 +55,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.WindowManagerWrapper; -import com.android.systemui.wm.DisplayWindowController; +import com.android.systemui.wm.DisplayController; import java.util.ArrayList; import java.util.List; @@ -230,7 +230,7 @@ public class PipManager implements BasePipManager { * Initializes {@link PipManager}. */ public void initialize(Context context, BroadcastDispatcher broadcastDispatcher, - DisplayWindowController displayWindowController) { + DisplayController displayController) { if (mInitialized) { return; } 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..557c64b7dfb9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -191,6 +191,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile.setLabel(tile.getLabel()); mTile.setSubtitle(tile.getSubtitle()); mTile.setContentDescription(tile.getContentDescription()); + mTile.setStateDescription(tile.getStateDescription()); mTile.setState(tile.getState()); } @@ -345,6 +346,12 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener state.contentDescription = state.label; } + if (mTile.getStateDescription() != null) { + state.stateDescription = mTile.getStateDescription(); + } else { + state.stateDescription = null; + } + if (state instanceof BooleanState) { state.expandedAccessibilityClassName = Switch.class.getName(); ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 8b7f280608a5..fda9e5b1f1ef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -65,7 +65,6 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private String mAccessibilityClass; private boolean mTileState; private boolean mCollapsedView; - private boolean mClicked; private boolean mShowRippleEffect = true; private final ImageView mBg; @@ -234,13 +233,35 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setLongClickable(state.handlesLongClick); mIcon.setIcon(state, allowAnimations); setContentDescription(state.contentDescription); + final StringBuilder stateDescription = new StringBuilder(); + switch (state.state) { + case Tile.STATE_UNAVAILABLE: + stateDescription.append(mContext.getString(R.string.tile_unavailable)); + break; + case Tile.STATE_INACTIVE: + if (state instanceof QSTile.BooleanState) { + stateDescription.append(mContext.getString(R.string.switch_bar_off)); + } + break; + case Tile.STATE_ACTIVE: + if (state instanceof QSTile.BooleanState) { + stateDescription.append(mContext.getString(R.string.switch_bar_on)); + } + break; + default: + break; + } + if (!TextUtils.isEmpty(state.stateDescription)) { + stateDescription.append(", "); + stateDescription.append(state.stateDescription); + } + setStateDescription(stateDescription.toString()); mAccessibilityClass = state.state == Tile.STATE_UNAVAILABLE ? null : state.expandedAccessibilityClassName; if (state instanceof QSTile.BooleanState) { boolean newState = ((BooleanState) state).value; if (mTileState != newState) { - mClicked = false; mTileState = newState; } } @@ -297,23 +318,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } @Override - public boolean performClick() { - mClicked = true; - return super.performClick(); - } - - @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); if (!TextUtils.isEmpty(mAccessibilityClass)) { event.setClassName(mAccessibilityClass); - if (Switch.class.getName().equals(mAccessibilityClass)) { - boolean b = mClicked ? !mTileState : mTileState; - String label = getResources() - .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off); - event.setContentDescription(label); - event.setChecked(b); - } } } @@ -325,11 +333,6 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { if (!TextUtils.isEmpty(mAccessibilityClass)) { info.setClassName(mAccessibilityClass); if (Switch.class.getName().equals(mAccessibilityClass)) { - boolean b = mClicked ? !mTileState : mTileState; - String label = getResources() - .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off); - info.setText(label); - info.setChecked(b); info.setCheckable(true); if (isLongClickable()) { info.addAction( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 9282a2e3b312..361b6c1b1260 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -134,25 +134,27 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { state.label = mContext.getString(R.string.quick_settings_bluetooth_label); state.secondaryLabel = TextUtils.emptyIfNull( getSecondaryLabel(enabled, connecting, connected, state.isTransient)); + state.contentDescription = state.label; + state.stateDescription = ""; if (enabled) { if (connected) { state.icon = new BluetoothConnectedTileIcon(); if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) { state.label = mController.getConnectedDeviceName(); } - state.contentDescription = + state.stateDescription = mContext.getString(R.string.accessibility_bluetooth_name, state.label) + ", " + state.secondaryLabel; } else if (state.isTransient) { state.icon = ResourceIcon.get( com.android.internal.R.drawable.ic_bluetooth_transient_animation); - state.contentDescription = state.secondaryLabel; + state.stateDescription = state.secondaryLabel; } else { state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth); state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_bluetooth) + "," - + mContext.getString(R.string.accessibility_not_connected); + R.string.accessibility_quick_settings_bluetooth); + state.stateDescription = mContext.getString(R.string.accessibility_not_connected); } state.state = Tile.STATE_ACTIVE; } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 0e813d1ab4e4..58de0575fa75 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -183,6 +183,7 @@ public class CastTile extends QSTileImpl<BooleanState> { protected void handleUpdateState(BooleanState state, Object arg) { state.label = mContext.getString(R.string.quick_settings_cast_title); state.contentDescription = state.label; + state.stateDescription = ""; state.value = false; final List<CastDevice> devices = mController.getCastDevices(); boolean connecting = false; @@ -192,8 +193,9 @@ public class CastTile extends QSTileImpl<BooleanState> { if (device.state == CastDevice.STATE_CONNECTED) { state.value = true; state.secondaryLabel = getDeviceName(device); - state.contentDescription = state.contentDescription + "," + - mContext.getString(R.string.accessibility_cast_name, state.label); + state.stateDescription = state.stateDescription + "," + + mContext.getString( + R.string.accessibility_cast_name, state.label); connecting = false; break; } else if (device.state == CastDevice.STATE_CONNECTING) { @@ -217,9 +219,8 @@ public class CastTile extends QSTileImpl<BooleanState> { state.state = Tile.STATE_UNAVAILABLE; String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi); state.secondaryLabel = noWifi; - state.contentDescription = state.contentDescription + ", " + mContext.getString( - R.string.accessibility_quick_settings_not_available, noWifi); } + state.stateDescription = state.stateDescription + ", " + state.secondaryLabel; mDetailAdapter.updateItems(devices); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index 22470c7f5af5..d5f86c951407 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -194,17 +194,13 @@ public class CellularTile extends QSTileImpl<SignalState> { state.secondaryLabel = r.getString(R.string.cell_data_off); } - - // TODO(b/77881974): Instead of switching out the description via a string check for - // we need to have two strings provided by the MobileIconGroup. - final CharSequence contentDescriptionSuffix; + state.contentDescription = state.label; if (state.state == Tile.STATE_INACTIVE) { - contentDescriptionSuffix = r.getString(R.string.cell_data_off_content_description); + // This information is appended later by converting the Tile.STATE_INACTIVE state. + state.stateDescription = ""; } else { - contentDescriptionSuffix = state.secondaryLabel; + state.stateDescription = state.secondaryLabel; } - - state.contentDescription = state.label + ", " + contentDescriptionSuffix; } private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 52d1a5b3b991..9215da4cda9a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -240,6 +240,8 @@ public class DndTile extends QSTileImpl<BooleanState> { zen != Global.ZEN_MODE_OFF, mController.getConfig(), false)); state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_dnd); checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME); + // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier + // to understand. switch (zen) { case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: state.contentDescription = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index dafdd89ee62c..792c36477962 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -102,14 +102,13 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements } state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label); state.secondaryLabel = ""; + state.stateDescription = ""; if (!mFlashlightController.isAvailable()) { state.icon = mIcon; state.slash.isSlashed = true; state.secondaryLabel = mContext.getString( R.string.quick_settings_flashlight_camera_in_use); - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_flashlight_unavailable) - + ", " + state.secondaryLabel; + state.stateDescription = state.secondaryLabel; state.state = Tile.STATE_UNAVAILABLE; return; } 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..fd6b936d71c0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -147,6 +147,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> { state.secondaryLabel = getSecondaryLabel( isTileActive, isTransient, isDataSaverEnabled, numConnectedDevices); + state.stateDescription = state.secondaryLabel; } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index fbdca3ba1c7b..e617867eb10e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -105,15 +105,8 @@ public class LocationTile extends QSTileImpl<BooleanState> { } state.icon = mIcon; state.slash.isSlashed = !state.value; - if (locationEnabled) { - state.label = mContext.getString(R.string.quick_settings_location_label); - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_location_on); - } else { - state.label = mContext.getString(R.string.quick_settings_location_label); - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_location_off); - } + state.label = mContext.getString(R.string.quick_settings_location_label); + state.contentDescription = state.label; state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.expandedAccessibilityClassName = Switch.class.getName(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index b7ce101cacab..6e8dcf36bacc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -195,6 +195,7 @@ public class WifiTile extends QSTileImpl<SignalState> { state.activityIn = cb.enabled && cb.activityIn; state.activityOut = cb.enabled && cb.activityOut; final StringBuffer minimalContentDescription = new StringBuffer(); + final StringBuffer minimalStateDescription = new StringBuffer(); final Resources r = mContext.getResources(); if (isTransient) { state.icon = ResourceIcon.get( @@ -219,13 +220,14 @@ public class WifiTile extends QSTileImpl<SignalState> { mContext.getString(R.string.quick_settings_wifi_label)).append(","); if (state.value) { if (wifiConnected) { - minimalContentDescription.append(cb.wifiSignalContentDescription).append(","); + minimalStateDescription.append(cb.wifiSignalContentDescription); minimalContentDescription.append(removeDoubleQuotes(cb.ssid)); if (!TextUtils.isEmpty(state.secondaryLabel)) { minimalContentDescription.append(",").append(state.secondaryLabel); } } } + state.stateDescription = minimalStateDescription.toString(); state.contentDescription = minimalContentDescription.toString(); state.dualLabelContentDescription = r.getString( R.string.accessibility_quick_settings_open_settings, getTileLabel()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index 7853dc388bcb..e54ee51fb9d4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -103,14 +103,11 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements state.icon = mIcon; if (state.value) { state.slash.isSlashed = false; - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_work_mode_on); } else { state.slash.isSlashed = true; - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_work_mode_off); } state.label = mContext.getString(R.string.quick_settings_work_mode_label); + state.contentDescription = state.label; state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 79a33c926993..573ea4dd85de 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -29,6 +29,7 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WIN import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED; import android.annotation.FloatRange; import android.app.ActivityTaskManager; @@ -67,6 +68,7 @@ import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.stackdivider.Divider; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.phone.NavigationBarFragment; import com.android.systemui.statusbar.phone.NavigationBarView; @@ -485,7 +487,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @Inject - public OverviewProxyService(Context context, DeviceProvisionedController provisionController, + public OverviewProxyService(Context context, CommandQueue commandQueue, + DeviceProvisionedController provisionController, NavigationBarController navBarController, NavigationModeController navModeController, NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, PipUI pipUI, Optional<Divider> dividerOptional, @@ -530,6 +533,15 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // Listen for status bar state changes statusBarWinController.registerCallback(mStatusBarWindowCallback); mScreenshotHelper = new ScreenshotHelper(context); + + // Listen for tracing state changes + commandQueue.addCallback(new CommandQueue.Callbacks() { + @Override + public void onTracingStateChanged(boolean enabled) { + mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled) + .commitUpdate(mContext.getDisplayId()); + } + }); } public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton, @@ -573,14 +585,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing) { - int displayId = mContext.getDisplayId(); - mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING, keyguardShowing && !keyguardOccluded) .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED, keyguardShowing && keyguardOccluded) .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing) - .commitUpdate(displayId); + .commitUpdate(mContext.getDisplayId()); } /** @@ -601,10 +611,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } - public float getBackButtonAlpha() { - return mNavBarButtonAlpha; - } - public void cleanupAfterDeath() { if (mInputFocusTransferStarted) { mHandler.post(()-> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 3af3701dff97..64f083024ce1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -53,6 +53,7 @@ import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.view.AppearanceRegion; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.policy.CallbackController; +import com.android.systemui.tracing.ProtoTracer; import java.util.ArrayList; @@ -123,6 +124,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< private static final int MSG_DISMISS_INATTENTIVE_SLEEP_WARNING = 52 << MSG_SHIFT; private static final int MSG_SHOW_TOAST = 53 << MSG_SHIFT; private static final int MSG_HIDE_TOAST = 54 << MSG_SHIFT; + private static final int MSG_TRACING_STATE_CHANGED = 55 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -143,6 +145,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< * event. */ private int mLastUpdatedImeDisplayId = INVALID_DISPLAY; + private ProtoTracer mProtoTracer; /** * These methods are called back on the main thread. @@ -325,9 +328,19 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< * @see IStatusBar#hideToast(String, IBinder) (String, IBinder) */ default void hideToast(String packageName, IBinder token) { } + + /** + * @param enabled + */ + default void onTracingStateChanged(boolean enabled) { } } public CommandQueue(Context context) { + this(context, null); + } + + public CommandQueue(Context context, ProtoTracer protoTracer) { + mProtoTracer = protoTracer; context.getSystemService(DisplayManager.class).registerDisplayListener(this, mHandler); // We always have default display. setDisabled(DEFAULT_DISPLAY, DISABLE_NONE, DISABLE2_NONE); @@ -917,6 +930,26 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } } + @Override + public void startTracing() { + synchronized (mLock) { + if (mProtoTracer != null) { + mProtoTracer.start(); + } + mHandler.obtainMessage(MSG_TRACING_STATE_CHANGED, true).sendToTarget(); + } + } + + @Override + public void stopTracing() { + synchronized (mLock) { + if (mProtoTracer != null) { + mProtoTracer.stop(); + } + mHandler.obtainMessage(MSG_TRACING_STATE_CHANGED, false).sendToTarget(); + } + } + private final class H extends Handler { private H(Looper l) { super(l); @@ -1244,6 +1277,11 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< } break; } + case MSG_TRACING_STATE_CHANGED: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).onTracingStateChanged((Boolean) msg.obj); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 6b0b5dfeaf40..8d4a9efbcd7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -140,7 +140,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle boolean hideMedia = Utils.useQsMediaPlayer(mContext); if (ent.isRowDismissed() || ent.isRowRemoved() || (ent.isMediaNotification() && hideMedia) - || mBubbleController.isBubbleNotificationSuppressedFromShade(ent.getKey())) { + || mBubbleController.isBubbleNotificationSuppressedFromShade(ent)) { // we don't want to update removed notifications because they could // temporarily become children if they were isolated before. continue; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java index ec8dbead7de2..493482aacce5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar; import android.content.Context; import com.android.systemui.statusbar.notification.row.NotificationRowModule; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.tracing.ProtoTracer; import javax.inject.Singleton; @@ -35,8 +37,8 @@ public class StatusBarDependenciesModule { */ @Provides @Singleton - public CommandQueue provideCommandQueue(Context context) { - return new CommandQueue(context); + public CommandQueue provideCommandQueue(Context context, ProtoTracer protoTracer) { + return new CommandQueue(context, protoTracer); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index 56ad0e1df36f..b048d032feaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -44,10 +44,11 @@ public abstract class ListEntry { /** * Should return the "representative entry" for this ListEntry. For NotificationEntries, its - * the entry itself. For groups, it should be the summary. This method exists to interface with + * the entry itself. For groups, it should be the summary (but if a summary doesn't exist, + * this can return null). This method exists to interface with * legacy code that expects groups to also be NotificationEntries. */ - public abstract NotificationEntry getRepresentativeEntry(); + public abstract @Nullable NotificationEntry getRepresentativeEntry(); @Nullable public GroupEntry getParent() { return mParent; 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 86c0d85829b5..8ac4d3008179 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 @@ -44,7 +44,6 @@ import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; -import android.util.Log; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.DumpController; @@ -56,6 +55,7 @@ import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoa import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; 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.NotifLifetimeExtender; import com.android.systemui.util.Assert; @@ -100,6 +100,7 @@ import javax.inject.Singleton; public class NotifCollection implements Dumpable { private final IStatusBarService mStatusBarService; private final FeatureFlags mFeatureFlags; + private final NotifCollectionLogger mLogger; private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>(); private final Collection<NotificationEntry> mReadOnlyNotificationSet = @@ -116,9 +117,11 @@ public class NotifCollection implements Dumpable { public NotifCollection( IStatusBarService statusBarService, DumpController dumpController, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + NotifCollectionLogger logger) { Assert.isMainThread(); mStatusBarService = statusBarService; + mLogger = logger; dumpController.registerDumpable(TAG, this); mFeatureFlags = featureFlags; } @@ -190,8 +193,8 @@ public class NotifCollection implements Dumpable { private void onNotificationGroupPosted(List<CoalescedEvent> batch) { Assert.isMainThread(); - Log.d(TAG, "POSTED GROUP " + batch.get(0).getSbn().getGroupKey() - + " (" + batch.size() + " events)"); + mLogger.logNotifGroupPosted(batch.get(0).getSbn().getGroupKey(), batch.size()); + for (CoalescedEvent event : batch) { postNotification(event.getSbn(), event.getRanking(), null); } @@ -204,13 +207,14 @@ public class NotifCollection implements Dumpable { int reason) { Assert.isMainThread(); - Log.d(TAG, "REMOVED " + sbn.getKey() + " reason=" + reason); + mLogger.logNotifRemoved(sbn.getKey(), reason); removeNotification(sbn.getKey(), rankingMap, reason, null); } private void onNotificationRankingUpdate(RankingMap rankingMap) { Assert.isMainThread(); applyRanking(rankingMap); + dispatchNotificationRankingUpdate(rankingMap); rebuildList(); } @@ -222,7 +226,7 @@ public class NotifCollection implements Dumpable { if (entry == null) { // A new notification! - Log.d(TAG, "POSTED " + sbn.getKey()); + mLogger.logNotifPosted(sbn.getKey()); entry = new NotificationEntry(sbn, ranking); mNotificationSet.put(sbn.getKey(), entry); @@ -234,7 +238,7 @@ public class NotifCollection implements Dumpable { } else { // Update to an existing entry - Log.d(TAG, "UPDATED " + sbn.getKey()); + mLogger.logNotifUpdated(sbn.getKey()); // Notification is updated so it is essentially re-added and thus alive again. Don't // need to keep its lifetime extended. @@ -390,6 +394,14 @@ public class NotifCollection implements Dumpable { mAmDispatchingToOtherCode = false; } + private void dispatchNotificationRankingUpdate(RankingMap map) { + mAmDispatchingToOtherCode = true; + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onRankingUpdate(map); + } + mAmDispatchingToOtherCode = false; + } + private void dispatchOnEntryRemoved( NotificationEntry entry, @CancellationReason int reason, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 97f8ec5f5bb7..9f8f42ee116c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -32,19 +32,20 @@ import android.annotation.Nullable; import android.util.ArrayMap; import android.util.Pair; +import androidx.annotation.NonNull; + import com.android.systemui.DumpController; import com.android.systemui.Dumpable; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState; +import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; 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.CollectionReadyForBuildListener; -import com.android.systemui.statusbar.notification.logging.NotifEvent; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.util.Assert; import com.android.systemui.util.time.SystemClock; @@ -69,7 +70,7 @@ import javax.inject.Singleton; @Singleton public class ShadeListBuilder implements Dumpable { private final SystemClock mSystemClock; - private final NotifLog mNotifLog; + private final ShadeListBuilderLogger mLogger; private List<ListEntry> mNotifList = new ArrayList<>(); private List<ListEntry> mNewNotifList = new ArrayList<>(); @@ -98,11 +99,11 @@ public class ShadeListBuilder implements Dumpable { @Inject public ShadeListBuilder( SystemClock systemClock, - NotifLog notifLog, + ShadeListBuilderLogger logger, DumpController dumpController) { Assert.isMainThread(); mSystemClock = systemClock; - mNotifLog = notifLog; + mLogger = logger; dumpController.registerDumpable(TAG, this); } @@ -205,8 +206,7 @@ public class ShadeListBuilder implements Dumpable { Assert.isMainThread(); mPipelineState.requireIsBefore(STATE_BUILD_STARTED); - mNotifLog.log(NotifEvent.ON_BUILD_LIST, "Request received from " - + "NotifCollection"); + mLogger.logOnBuildList(); mAllEntries = entries; buildList(); } @@ -215,21 +215,15 @@ public class ShadeListBuilder implements Dumpable { private void onPreGroupFilterInvalidated(NotifFilter filter) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.PRE_GROUP_FILTER_INVALIDATED, String.format( - "Filter \"%s\" invalidated; pipeline state is %d", - filter.getName(), - mPipelineState.getState())); + mLogger.logPreGroupFilterInvalidated(filter.getName(), mPipelineState.getState()); rebuildListIfBefore(STATE_PRE_GROUP_FILTERING); } - private void onPromoterInvalidated(NotifPromoter filter) { + private void onPromoterInvalidated(NotifPromoter promoter) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.PROMOTER_INVALIDATED, String.format( - "NotifPromoter \"%s\" invalidated; pipeline state is %d", - filter.getName(), - mPipelineState.getState())); + mLogger.logPromoterInvalidated(promoter.getName(), mPipelineState.getState()); rebuildListIfBefore(STATE_TRANSFORMING); } @@ -237,10 +231,7 @@ public class ShadeListBuilder implements Dumpable { private void onNotifSectionInvalidated(NotifSection section) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.SECTION_INVALIDATED, String.format( - "Section \"%s\" invalidated; pipeline state is %d", - section.getName(), - mPipelineState.getState())); + mLogger.logNotifSectionInvalidated(section.getName(), mPipelineState.getState()); rebuildListIfBefore(STATE_SORTING); } @@ -248,10 +239,7 @@ public class ShadeListBuilder implements Dumpable { private void onPreRenderFilterInvalidated(NotifFilter filter) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.PRE_RENDER_FILTER_INVALIDATED, String.format( - "Filter \"%s\" invalidated; pipeline state is %d", - filter.getName(), - mPipelineState.getState())); + mLogger.logPreRenderFilterInvalidated(filter.getName(), mPipelineState.getState()); rebuildListIfBefore(STATE_PRE_RENDER_FILTERING); } @@ -259,10 +247,7 @@ public class ShadeListBuilder implements Dumpable { private void onNotifComparatorInvalidated(NotifComparator comparator) { Assert.isMainThread(); - mNotifLog.log(NotifEvent.COMPARATOR_INVALIDATED, String.format( - "Comparator \"%s\" invalidated; pipeline state is %d", - comparator.getName(), - mPipelineState.getState())); + mLogger.logNotifComparatorInvalidated(comparator.getName(), mPipelineState.getState()); rebuildListIfBefore(STATE_SORTING); } @@ -288,7 +273,7 @@ public class ShadeListBuilder implements Dumpable { * if we detect that behavior, we should crash instantly. */ private void buildList() { - mNotifLog.log(NotifEvent.START_BUILD_LIST, "Run #" + mIterationCount + "..."); + mLogger.logStartBuildList(mIterationCount); mPipelineState.requireIsBefore(STATE_BUILD_STARTED); mPipelineState.setState(STATE_BUILD_STARTED); @@ -334,16 +319,16 @@ public class ShadeListBuilder implements Dumpable { freeEmptyGroups(); // Step 6: Dispatch the new list, first to any listeners and then to the view layer - mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n" - + ListDumper.dumpTree(mNotifList, false, "\t\t")); + if (mIterationCount % 10 == 0) { + mLogger.logFinalList(mNotifList); + } dispatchOnBeforeRenderList(mReadOnlyNotifList); if (mOnRenderListListener != null) { mOnRenderListListener.onRenderList(mReadOnlyNotifList); } // Step 7: We're done! - mNotifLog.log(NotifEvent.LIST_BUILD_COMPLETE, - "Notif list build #" + mIterationCount + " completed"); + mLogger.logEndBuildList(mIterationCount); mPipelineState.setState(STATE_IDLE); mIterationCount++; } @@ -429,11 +414,10 @@ public class ShadeListBuilder implements Dumpable { if (existingSummary == null) { group.setSummary(entry); } else { - mNotifLog.log(NotifEvent.WARN, String.format( - "Duplicate summary for group '%s': '%s' vs. '%s'", + mLogger.logDuplicateSummary( group.getKey(), existingSummary.getKey(), - entry.getKey())); + entry.getKey()); // Use whichever one was posted most recently if (entry.getSbn().getPostTime() @@ -452,8 +436,7 @@ public class ShadeListBuilder implements Dumpable { final String topLevelKey = entry.getKey(); if (mGroups.containsKey(topLevelKey)) { - mNotifLog.log(NotifEvent.WARN, - "Duplicate non-group top-level key: " + topLevelKey); + mLogger.logDuplicateTopLevelKey(topLevelKey); } else { entry.setParent(ROOT_ENTRY); out.add(entry); @@ -617,24 +600,22 @@ public class ShadeListBuilder implements Dumpable { private void logParentingChanges() { for (NotificationEntry entry : mAllEntries) { if (entry.getParent() != entry.getPreviousParent()) { - mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format( - "%s: parent changed from %s to %s", + mLogger.logParentChanged( entry.getKey(), entry.getPreviousParent() == null - ? "null" : entry.getPreviousParent().getKey(), + ? null : entry.getPreviousParent().getKey(), entry.getParent() == null - ? "null" : entry.getParent().getKey())); + ? null : entry.getParent().getKey()); } } for (GroupEntry group : mGroups.values()) { if (group.getParent() != group.getPreviousParent()) { - mNotifLog.log(NotifEvent.PARENT_CHANGED, String.format( - "%s: parent changed from %s to %s", + mLogger.logParentChanged( group.getKey(), group.getPreviousParent() == null - ? "null" : group.getPreviousParent().getKey(), + ? null : group.getPreviousParent().getKey(), group.getParent() == null - ? "null" : group.getParent().getKey())); + ? null : group.getParent().getKey()); } } } @@ -684,23 +665,10 @@ public class ShadeListBuilder implements Dumpable { NotifFilter filter = findRejectingFilter(entry, now, filters); if (filter != entry.mExcludingFilter) { - if (entry.mExcludingFilter == null) { - mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format( - "%s: filtered out by '%s'", - entry.getKey(), - filter.getName())); - } else if (filter == null) { - mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format( - "%s: no longer filtered out (previous filter was '%s')", - entry.getKey(), - entry.mExcludingFilter.getName())); - } else { - mNotifLog.log(NotifEvent.FILTER_CHANGED, String.format( - "%s: filter changed: '%s' -> '%s'", - entry.getKey(), - entry.mExcludingFilter, - filter)); - } + mLogger.logFilterChanged( + entry.getKey(), + entry.mExcludingFilter != null ? entry.mExcludingFilter.getName() : null, + filter != null ? filter.getName() : null); // Note that groups and summaries can also be filtered out later if they're part of a // malformed group. We currently don't have a great way to track that beyond parenting @@ -728,23 +696,10 @@ public class ShadeListBuilder implements Dumpable { NotifPromoter promoter = findPromoter(entry); if (promoter != entry.mNotifPromoter) { - if (entry.mNotifPromoter == null) { - mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format( - "%s: Entry promoted to top level by '%s'", - entry.getKey(), - promoter.getName())); - } else if (promoter == null) { - mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format( - "%s: Entry is no longer promoted to top level (previous promoter was '%s')", - entry.getKey(), - entry.mNotifPromoter.getName())); - } else { - mNotifLog.log(NotifEvent.PROMOTER_CHANGED, String.format( - "%s: Top-level promoter changed: '%s' -> '%s'", - entry.getKey(), - entry.mNotifPromoter, - promoter)); - } + mLogger.logPromoterChanged( + entry.getKey(), + entry.mNotifPromoter != null ? entry.mNotifPromoter.getName() : null, + promoter != null ? promoter.getName() : null); entry.mNotifPromoter = promoter; } @@ -767,21 +722,12 @@ public class ShadeListBuilder implements Dumpable { final Integer sectionIndex = sectionWithIndex.second; if (section != entry.mNotifSection) { - if (entry.mNotifSection == null) { - mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format( - "%s: sectioned by '%s' [index=%d].", - entry.getKey(), - section.getName(), - sectionIndex)); - } else { - mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format( - "%s: section changed: '%s' [index=%d] -> '%s [index=%d]'.", - entry.getKey(), - entry.mNotifSection, - entry.getSection(), - section, - sectionIndex)); - } + mLogger.logSectionChanged( + entry.getKey(), + entry.mNotifSection != null ? entry.mNotifSection.getName() : null, + entry.getSection(), + section.getName(), + sectionIndex); entry.mNotifSection = section; entry.setSection(sectionIndex); @@ -826,7 +772,7 @@ public class ShadeListBuilder implements Dumpable { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + public void dump(@NonNull FileDescriptor fd, PrintWriter pw, @NonNull String[] args) { pw.println("\t" + TAG + " shade notifications:"); if (getShadeList().size() == 0) { pw.println("\t\t None"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index f5890386a14f..98c45ffd6afb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -16,11 +16,6 @@ package com.android.systemui.statusbar.notification.collection.coalescer; -import static com.android.systemui.statusbar.notification.logging.NotifEvent.BATCH_MAX_TIMEOUT; -import static com.android.systemui.statusbar.notification.logging.NotifEvent.COALESCED_EVENT; -import static com.android.systemui.statusbar.notification.logging.NotifEvent.EARLY_BATCH_EMIT; -import static com.android.systemui.statusbar.notification.logging.NotifEvent.EMIT_EVENT_BATCH; - import static java.util.Objects.requireNonNull; import android.annotation.MainThread; @@ -35,7 +30,6 @@ import com.android.systemui.Dumpable; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; @@ -71,7 +65,7 @@ import javax.inject.Inject; public class GroupCoalescer implements Dumpable { private final DelayableExecutor mMainExecutor; private final SystemClock mClock; - private final NotifLog mLog; + private final GroupCoalescerLogger mLogger; private final long mMinGroupLingerDuration; private final long mMaxGroupLingerDuration; @@ -83,8 +77,9 @@ public class GroupCoalescer implements Dumpable { @Inject public GroupCoalescer( @Main DelayableExecutor mainExecutor, - SystemClock clock, NotifLog log) { - this(mainExecutor, clock, log, MIN_GROUP_LINGER_DURATION, MAX_GROUP_LINGER_DURATION); + SystemClock clock, + GroupCoalescerLogger logger) { + this(mainExecutor, clock, logger, MIN_GROUP_LINGER_DURATION, MAX_GROUP_LINGER_DURATION); } /** @@ -98,12 +93,12 @@ public class GroupCoalescer implements Dumpable { GroupCoalescer( @Main DelayableExecutor mainExecutor, SystemClock clock, - NotifLog log, + GroupCoalescerLogger logger, long minGroupLingerDuration, long maxGroupLingerDuration) { mMainExecutor = mainExecutor; mClock = clock; - mLog = log; + mLogger = logger; mMinGroupLingerDuration = minGroupLingerDuration; mMaxGroupLingerDuration = maxGroupLingerDuration; } @@ -129,7 +124,7 @@ public class GroupCoalescer implements Dumpable { final boolean shouldCoalesce = handleNotificationPosted(sbn, rankingMap); if (shouldCoalesce) { - mLog.log(COALESCED_EVENT, String.format("Coalesced notification %s", sbn.getKey())); + mLogger.logEventCoalesced(sbn.getKey()); mHandler.onNotificationRankingUpdate(rankingMap); } else { mHandler.onNotificationPosted(sbn, rankingMap); @@ -164,15 +159,11 @@ public class GroupCoalescer implements Dumpable { final CoalescedEvent event = mCoalescedEvents.get(sbn.getKey()); final EventBatch batch = mBatches.get(sbn.getGroupKey()); if (event != null) { - mLog.log(EARLY_BATCH_EMIT, - String.format("Modification of %s triggered early emit of batched group %s", - sbn.getKey(), requireNonNull(event.getBatch()).mGroupKey)); + mLogger.logEarlyEmit(sbn.getKey(), requireNonNull(event.getBatch()).mGroupKey); emitBatch(requireNonNull(event.getBatch())); } else if (batch != null && mClock.uptimeMillis() - batch.mCreatedTimestamp >= mMaxGroupLingerDuration) { - mLog.log(BATCH_MAX_TIMEOUT, - String.format("Modification of %s triggered timeout emit of batched group %s", - sbn.getKey(), batch.mGroupKey)); + mLogger.logMaxBatchTimeout(sbn.getKey(), batch.mGroupKey); emitBatch(batch); } } @@ -253,7 +244,7 @@ public class GroupCoalescer implements Dumpable { } events.sort(mEventComparator); - mLog.log(EMIT_EVENT_BATCH, "Emitting event batch for group " + batch.mGroupKey); + mLogger.logEmitBatch(batch.mGroupKey); mHandler.onNotificationBatchPosted(events); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt new file mode 100644 index 000000000000..6e8788db59d7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt @@ -0,0 +1,62 @@ +/* + * 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.coalescer + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class GroupCoalescerLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logEventCoalesced(key: String) { + buffer.log(TAG, LogLevel.INFO, { + str1 = key + }, { + "COALESCED: $str1" + }) + } + + fun logEmitBatch(groupKey: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = groupKey + }, { + "Emitting event batch for group $str1" + }) + } + + fun logEarlyEmit(modifiedKey: String, groupKey: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = modifiedKey + str2 = groupKey + }, { + "Modification of notif $str1 triggered early emit of batched group $str2" + }) + } + + fun logMaxBatchTimeout(modifiedKey: String, groupKey: String) { + buffer.log(TAG, LogLevel.INFO, { + str1 = modifiedKey + str2 = groupKey + }, { + "Modification of notif $str1 triggered TIMEOUT emit of batched group $str2" + }) + } +} + +private const val TAG = "GroupCoalescer"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt new file mode 100644 index 000000000000..6e15043973f7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -0,0 +1,217 @@ +/* + * 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.listbuilder + +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.LogLevel.WARNING +import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry +import javax.inject.Inject + +class ShadeListBuilderLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logOnBuildList() { + buffer.log(TAG, INFO, { + }, { + "Request received from NotifCollection" + }) + } + + fun logStartBuildList(iterationCount: Int) { + buffer.log(TAG, INFO, { + int1 = iterationCount + }, { + "Starting to build shade list (run #$int1)" + }) + } + + fun logEndBuildList(iterationCount: Int) { + buffer.log(TAG, INFO, { + int1 = iterationCount + }, { + "Finished building shade list (run #$int1)" + }) + } + + fun logPreGroupFilterInvalidated(filterName: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = filterName + int1 = pipelineState + }, { + """Pre-group NotifFilter "$str1" invalidated; pipeline state is $int1""" + }) + } + + fun logPromoterInvalidated(name: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = name + int1 = pipelineState + }, { + """NotifPromoter "$str1" invalidated; pipeline state is $int1""" + }) + } + + fun logNotifSectionInvalidated(name: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = name + int1 = pipelineState + }, { + """NotifSection "$str1" invalidated; pipeline state is $int1""" + }) + } + + fun logNotifComparatorInvalidated(name: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = name + int1 = pipelineState + }, { + """NotifComparator "$str1" invalidated; pipeline state is $int1""" + }) + } + + fun logPreRenderFilterInvalidated(name: String, pipelineState: Int) { + buffer.log(TAG, DEBUG, { + str1 = name + int1 = pipelineState + }, { + """Pre-render NotifFilter "$str1" invalidated; pipeline state is $int1""" + }) + } + + fun logDuplicateSummary(groupKey: String, existingKey: String, newKey: String) { + buffer.log(TAG, WARNING, { + str1 = groupKey + str2 = existingKey + str3 = newKey + }, { + """Duplicate summary for group "$str1": "$str2" vs. "$str3"""" + }) + } + + fun logDuplicateTopLevelKey(topLevelKey: String) { + buffer.log(TAG, WARNING, { + str1 = topLevelKey + }, { + "Duplicate top-level key: $str1" + }) + } + + fun logParentChanged(key: String, prevParent: String?, newParent: String?) { + buffer.log(TAG, INFO, { + str1 = key + str2 = prevParent + str3 = newParent + }, { + "Parent change for $str1: $str2 -> $str3" + }) + } + + fun logFilterChanged( + key: String, + prevFilter: String?, + newFilter: String? + ) { + buffer.log(TAG, INFO, { + str1 = key + str2 = prevFilter + str3 = newFilter + }, { + "Filter changed for $str1: $str2 -> $str3" + }) + } + + fun logPromoterChanged( + key: String, + prevPromoter: String?, + newPromoter: String? + ) { + buffer.log(TAG, INFO, { + str1 = key + str2 = prevPromoter + str3 = newPromoter + }, { + "Promoter changed for $str1: $str2 -> $str3" + }) + } + + fun logSectionChanged( + key: String, + prevSection: String?, + prevIndex: Int, + section: String, + index: Int + ) { + buffer.log(TAG, INFO, { + str1 = key + str2 = section + int1 = index + str3 = prevSection + int2 = prevIndex + }, { + if (str3 == null) { + "Section assigned for $str1: '$str2' (#$int1)" + } else { + "Section changed for $str1: '$str3' (#$int2) -> '$str2' (#$int1)" + } + }) + } + + fun logFinalList(entries: List<ListEntry>) { + buffer.log(TAG, DEBUG, { + int1 = entries.size + }, { + "List is finalized ($int1 top-level entries):" + }) + if (entries.isEmpty()) { + buffer.log(TAG, DEBUG, {}, { "(empty list)" }) + } + for (i in entries.indices) { + val entry = entries[i] + buffer.log(TAG, DEBUG, { + int1 = i + str1 = entry.key + }, { + "[$int1] $str1" + }) + + if (entry is GroupEntry) { + entry.summary?.let { + buffer.log(TAG, DEBUG, { + str1 = it.key + }, { + " [*] $str1 (summary)" + }) + } + for (j in entry.children.indices) { + val child = entry.children[j] + buffer.log(TAG, DEBUG, { + int1 = j + str1 = child.key + }, { + " [$int1] $str1" + }) + } + } + } + } +} + +private const val TAG = "ShadeListBuilder"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 9cbc7d7efa66..6adcabd86fe6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection; +import android.service.notification.NotificationListenerService; + import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -47,4 +49,11 @@ public interface NotifCollectionListener { @CancellationReason int reason, boolean removedByUser) { } + + /** + * Called whenever the RankingMap is updated by system server. By the time this listener is + * called, the Rankings of all entries will have been updated. + */ + default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) { + } } 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 new file mode 100644 index 000000000000..bd1bd860f80c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -0,0 +1,62 @@ +/* + * 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.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationLog +import javax.inject.Inject + +class NotifCollectionLogger @Inject constructor( + @NotificationLog private val buffer: LogBuffer +) { + fun logNotifPosted(key: String) { + buffer.log(TAG, LogLevel.INFO, { + str1 = key + }, { + "POSTED $str1" + }) + } + + fun logNotifGroupPosted(groupKey: String, batchSize: Int) { + buffer.log(TAG, LogLevel.INFO, { + str1 = groupKey + int1 = batchSize + }, { + "POSTED GROUP $str1 ($int1 events)" + }) + } + + fun logNotifUpdated(key: String) { + buffer.log(TAG, LogLevel.INFO, { + str1 = key + }, { + "UPDATED $str1" + }) + } + + fun logNotifRemoved(key: String, reason: Int) { + buffer.log(TAG, LogLevel.INFO, { + str1 = key + int1 = reason + }, { + "REMOVED $str1 reason=$int1" + }) + } +} + +private const val TAG = "NotifCollection"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java index b49611688bc7..0d9beaefbf8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -63,6 +63,10 @@ public class HighPriorityProvider { } final NotificationEntry notifEntry = entry.getRepresentativeEntry(); + if (notifEntry == null) { + return false; + } + return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT || hasHighPriorityCharacteristics(notifEntry) || hasHighPriorityChild(entry); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java index 6aadcb7b30e1..28f4136d5ecb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java @@ -92,9 +92,7 @@ public abstract class ExpandableOutlineView extends ExpandableView { outline.setRect(left, top, right, bottom); } else { Path clipPath = getClipPath(false /* ignoreTranslation */); - if (clipPath != null && clipPath.isConvex()) { - // The path might not be convex in border cases where the view is small and - // clipped + if (clipPath != null) { outline.setConvexPath(clipPath); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 18c755da53ff..6045524f30b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -29,6 +29,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationConver import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME; import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE; import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE; +import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_UNBUBBLE; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -121,7 +122,7 @@ public class NotificationConversationInfo extends LinearLayout implements boolean mSkipPost = false; private OnClickListener mOnBubbleClick = v -> { - mSelectedAction = ACTION_BUBBLE; + mSelectedAction = mStartedAsBubble ? ACTION_UNBUBBLE : ACTION_BUBBLE; if (mStartedAsBubble) { mBubbleController.onUserDemotedBubbleFromNotification(mEntry); } else { @@ -586,6 +587,7 @@ public class NotificationConversationInfo extends LinearLayout implements static final int ACTION_SNOOZE = 3; static final int ACTION_MUTE = 4; static final int ACTION_DEMOTE = 5; + static final int ACTION_UNBUBBLE = 6; private final INotificationManager mINotificationManager; private final String mAppPkg; @@ -606,9 +608,17 @@ public class NotificationConversationInfo extends LinearLayout implements @Override public void run() { try { + boolean channelSettingChanged = mAction != ACTION_HOME && mAction != ACTION_SNOOZE; switch (mAction) { case ACTION_BUBBLE: - mChannelToUpdate.setAllowBubbles(!mChannelToUpdate.canBubble()); + case ACTION_UNBUBBLE: + boolean canBubble = mAction == ACTION_BUBBLE; + if (mChannelToUpdate.canBubble() != canBubble) { + channelSettingChanged = true; + mChannelToUpdate.setAllowBubbles(canBubble); + } else { + channelSettingChanged = false; + } break; case ACTION_FAVORITE: // TODO: extend beyond DND @@ -629,7 +639,7 @@ public class NotificationConversationInfo extends LinearLayout implements } - if (mAction != ACTION_HOME && mAction != ACTION_SNOOZE) { + if (channelSettingChanged) { mINotificationManager.updateNotificationChannelForPackage( mAppPkg, mAppUid, mChannelToUpdate); } 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 6bd122d97dea..db692c8a8c89 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -54,6 +54,10 @@ import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.shared.tracing.ProtoTraceable; +import com.android.systemui.tracing.ProtoTracer; +import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; +import com.android.systemui.tracing.nano.SystemUiTraceProto; import java.io.PrintWriter; import java.util.concurrent.Executor; @@ -62,7 +66,7 @@ import java.util.concurrent.Executor; * Utility class to handle edge swipes for back gesture */ public class EdgeBackGestureHandler implements DisplayListener, - PluginListener<NavigationEdgeBackPlugin> { + PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> { private static final String TAG = "EdgeBackGestureHandler"; private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt( @@ -161,6 +165,7 @@ public class EdgeBackGestureHandler implements DisplayListener, mMainExecutor = context.getMainExecutor(); mOverviewProxyService = overviewProxyService; mPluginManager = pluginManager; + Dependency.get(ProtoTracer.class).add(this); // Reduce the default touch slop to ensure that we can intercept the gesture // before the app starts to react to it. @@ -399,6 +404,8 @@ public class EdgeBackGestureHandler implements DisplayListener, // forward touch mEdgeBackPlugin.onMotionEvent(ev); } + + Dependency.get(ProtoTracer.class).update(); } @Override @@ -458,6 +465,14 @@ public class EdgeBackGestureHandler implements DisplayListener, pw.println(" mEdgeWidth=" + mEdgeWidth); } + @Override + public void writeToProto(SystemUiTraceProto proto) { + if (proto.edgeBackGestureHandler == null) { + proto.edgeBackGestureHandler = new EdgeBackGestureHandlerProto(); + } + proto.edgeBackGestureHandler.allowGesture = mAllowGesture; + } + class SysUiInputEventReceiver extends InputEventReceiver { SysUiInputEventReceiver(InputChannel channel, Looper looper) { super(channel, looper); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java index 8c947edd9a83..77337e95bb95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java @@ -204,8 +204,8 @@ public class NotificationGroupManager implements OnHeadsUpChangedListener, State } int childCount = 0; boolean hasBubbles = false; - for (String key : group.children.keySet()) { - if (!getBubbleController().isBubbleNotificationSuppressedFromShade(key)) { + for (NotificationEntry entry : group.children.values()) { + if (!getBubbleController().isBubbleNotificationSuppressedFromShade(entry)) { childCount++; } else { hasBubbles = true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 707138ee8dc0..b09ccffdc4de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -1,5 +1,6 @@ package com.android.systemui.statusbar.phone; +import android.app.NotificationManager; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; @@ -10,6 +11,7 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.collection.ArrayMap; import com.android.internal.statusbar.StatusBarIcon; @@ -21,6 +23,7 @@ import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CrossFadeHelper; +import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarIconView; @@ -76,6 +79,19 @@ public class NotificationIconAreaController implements DarkReceiver, private boolean mFullyHidden; private boolean mAodIconsVisible; private boolean mIsPulsing; + private boolean mShowLowPriority = true; + + @VisibleForTesting + final NotificationListener.NotificationSettingsListener mSettingsListener = + new NotificationListener.NotificationSettingsListener() { + @Override + public void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { + mShowLowPriority = !hideSilentStatusIcons; + if (mNotificationScrollLayout != null) { + updateStatusBarIcons(); + } + } + }; public NotificationIconAreaController( Context context, @@ -84,6 +100,7 @@ public class NotificationIconAreaController implements DarkReceiver, NotificationWakeUpCoordinator wakeUpCoordinator, KeyguardBypassController keyguardBypassController, NotificationMediaManager notificationMediaManager, + NotificationListener notificationListener, DozeParameters dozeParameters) { mStatusBar = statusBar; mContrastColorUtil = ContrastColorUtil.getInstance(context); @@ -95,6 +112,7 @@ public class NotificationIconAreaController implements DarkReceiver, mWakeUpCoordinator = wakeUpCoordinator; wakeUpCoordinator.addListener(this); mBypassController = keyguardBypassController; + notificationListener.addNotificationSettingsListener(mSettingsListener); initializeNotificationAreaViews(context); reloadAodColor(); @@ -230,7 +248,7 @@ public class NotificationIconAreaController implements DarkReceiver, } protected boolean shouldShowNotificationIcon(NotificationEntry entry, - boolean showAmbient, boolean hideDismissed, + boolean showAmbient, boolean showLowPriority, boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon, boolean hidePulsing, boolean onlyShowCenteredIcon) { @@ -249,6 +267,9 @@ public class NotificationIconAreaController implements DarkReceiver, if (hideCurrentMedia && entry.getKey().equals(mMediaManager.getMediaNotificationKey())) { return false; } + if (!showLowPriority && entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) { + return false; + } if (!entry.isTopLevelChild()) { return false; } @@ -288,6 +309,7 @@ public class NotificationIconAreaController implements DarkReceiver, private void updateShelfIcons() { updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons, true /* showAmbient */, + true /* showLowPriority */, false /* hideDismissed */, false /* hideRepliedMessages */, false /* hideCurrentMedia */, @@ -299,6 +321,7 @@ public class NotificationIconAreaController implements DarkReceiver, public void updateStatusBarIcons() { updateIconsForLayout(entry -> entry.icon, mNotificationIcons, false /* showAmbient */, + mShowLowPriority, true /* hideDismissed */, true /* hideRepliedMessages */, false /* hideCurrentMedia */, @@ -310,6 +333,7 @@ public class NotificationIconAreaController implements DarkReceiver, private void updateCenterIcon() { updateIconsForLayout(entry -> entry.centeredIcon, mCenteredIcon, false /* showAmbient */, + true /* showLowPriority */, false /* hideDismissed */, false /* hideRepliedMessages */, false /* hideCurrentMedia */, @@ -321,6 +345,7 @@ public class NotificationIconAreaController implements DarkReceiver, public void updateAodNotificationIcons() { updateIconsForLayout(entry -> entry.aodIcon, mAodIcons, false /* showAmbient */, + true /* showLowPriority */, true /* hideDismissed */, true /* hideRepliedMessages */, true /* hideCurrentMedia */, @@ -329,18 +354,24 @@ public class NotificationIconAreaController implements DarkReceiver, false /* onlyShowCenteredIcon */); } + @VisibleForTesting + boolean shouldShouldLowPriorityIcons() { + return mShowLowPriority; + } + /** * Updates the notification icons for a host layout. This will ensure that the notification * host layout will have the same icons like the ones in here. * @param function A function to look up an icon view based on an entry * @param hostLayout which layout should be updated * @param showAmbient should ambient notification icons be shown + * @param showLowPriority should icons from silent notifications be shown * @param hideDismissed should dismissed icons be hidden * @param hideRepliedMessages should messages that have been replied to be hidden * @param hidePulsing should pulsing notifications be hidden */ private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function, - NotificationIconContainer hostLayout, boolean showAmbient, + NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority, boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia, boolean hideCenteredIcon, boolean hidePulsing, boolean onlyShowCenteredIcon) { ArrayList<StatusBarIconView> toShow = new ArrayList<>( @@ -351,7 +382,7 @@ public class NotificationIconAreaController implements DarkReceiver, View view = mNotificationScrollLayout.getChildAt(i); if (view instanceof ExpandableNotificationRow) { NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry(); - if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed, + if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed, hideRepliedMessages, hideCurrentMedia, hideCenteredIcon, hidePulsing, onlyShowCenteredIcon)) { StatusBarIconView iconView = function.apply(ent); diff --git a/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java new file mode 100644 index 000000000000..3bef044a2526 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tracing/ProtoTracer.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.tracing; + +import static com.android.systemui.tracing.nano.SystemUiTraceFileProto.MAGIC_NUMBER_H; +import static com.android.systemui.tracing.nano.SystemUiTraceFileProto.MAGIC_NUMBER_L; + +import android.content.Context; +import android.os.SystemClock; + +import androidx.annotation.NonNull; + +import com.android.systemui.DumpController; +import com.android.systemui.Dumpable; +import com.android.systemui.shared.tracing.FrameProtoTracer; +import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams; +import com.android.systemui.shared.tracing.ProtoTraceable; +import com.android.systemui.tracing.nano.SystemUiTraceProto; +import com.android.systemui.tracing.nano.SystemUiTraceEntryProto; +import com.android.systemui.tracing.nano.SystemUiTraceFileProto; + +import com.google.protobuf.nano.MessageNano; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Queue; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Controller for coordinating winscope proto tracing. + */ +@Singleton +public class ProtoTracer implements Dumpable, ProtoTraceParams<MessageNano, SystemUiTraceFileProto, + SystemUiTraceEntryProto, SystemUiTraceProto> { + + private static final String TAG = "ProtoTracer"; + private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; + + private final Context mContext; + private final FrameProtoTracer<MessageNano, SystemUiTraceFileProto, SystemUiTraceEntryProto, + SystemUiTraceProto> mProtoTracer; + + @Inject + public ProtoTracer(Context context, DumpController dumpController) { + mContext = context; + mProtoTracer = new FrameProtoTracer<>(this); + dumpController.registerDumpable(this); + } + + @Override + public File getTraceFile() { + return new File(mContext.getFilesDir(), "sysui_trace.pb"); + } + + @Override + public SystemUiTraceFileProto getEncapsulatingTraceProto() { + return new SystemUiTraceFileProto(); + } + + @Override + public SystemUiTraceEntryProto updateBufferProto(SystemUiTraceEntryProto reuseObj, + ArrayList<ProtoTraceable<SystemUiTraceProto>> traceables) { + SystemUiTraceEntryProto proto = reuseObj != null + ? reuseObj + : new SystemUiTraceEntryProto(); + proto.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); + proto.systemUi = proto.systemUi != null ? proto.systemUi : new SystemUiTraceProto(); + for (ProtoTraceable t : traceables) { + t.writeToProto(proto.systemUi); + } + return proto; + } + + @Override + public byte[] serializeEncapsulatingProto(SystemUiTraceFileProto encapsulatingProto, + Queue<SystemUiTraceEntryProto> buffer) { + encapsulatingProto.magicNumber = MAGIC_NUMBER_VALUE; + encapsulatingProto.entry = buffer.toArray(new SystemUiTraceEntryProto[0]); + return MessageNano.toByteArray(encapsulatingProto); + } + + @Override + public byte[] getProtoBytes(MessageNano proto) { + return MessageNano.toByteArray(proto); + } + + @Override + public int getProtoSize(MessageNano proto) { + return proto.getCachedSize(); + } + + public void start() { + mProtoTracer.start(); + } + + public void stop() { + mProtoTracer.stop(); + } + + public boolean isEnabled() { + return mProtoTracer.isEnabled(); + } + + public void add(ProtoTraceable<SystemUiTraceProto> traceable) { + mProtoTracer.add(traceable); + } + + public void remove(ProtoTraceable<SystemUiTraceProto> traceable) { + mProtoTracer.remove(traceable); + } + + public void scheduleFrameUpdate() { + mProtoTracer.scheduleFrameUpdate(); + } + + public void update() { + mProtoTracer.update(); + } + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("ProtoTracer:"); + pw.print(" "); pw.println("enabled: " + mProtoTracer.isEnabled()); + pw.print(" "); pw.println("usagePct: " + mProtoTracer.getBufferUsagePct()); + pw.print(" "); pw.println("file: " + getTraceFile()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto new file mode 100644 index 000000000000..08ae99ceb7a1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package com.android.systemui.tracing; + +option java_multiple_files = true; + +message SystemUiTraceProto { + + optional EdgeBackGestureHandlerProto edge_back_gesture_handler = 1; +} + +message EdgeBackGestureHandlerProto { + + optional bool allow_gesture = 1; +} diff --git a/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace_file.proto b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace_file.proto new file mode 100644 index 000000000000..d1523ef13501 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace_file.proto @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +import "frameworks/base/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto"; + +package com.android.systemui.tracing; + +option java_multiple_files = true; + +/* represents a file full of system ui trace entries. + Encoded, it should start with 0x9 0x53 0x59 0x53 0x55 0x49 0x54 0x52 0x43 (.SYSUITRC), such + that they can be easily identified. */ +message SystemUiTraceFileProto { + + /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L + (this is needed because enums have to be 32 bits and there's no nice way to put 64bit + constants into .proto files. */ + enum MagicNumber { + INVALID = 0; + MAGIC_NUMBER_L = 0x55535953; /* SYSU (little-endian ASCII) */ + MAGIC_NUMBER_H = 0x43525449; /* ITRC (little-endian ASCII) */ + } + + optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */ + repeated SystemUiTraceEntryProto entry = 2; +} + +/* one system ui trace entry. */ +message SystemUiTraceEntryProto { + /* required: elapsed realtime in nanos since boot of when this entry was logged */ + optional fixed64 elapsed_realtime_nanos = 1; + + optional SystemUiTraceProto system_ui = 3; +} diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayChangeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayChangeController.java new file mode 100644 index 000000000000..66bc306d4a59 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayChangeController.java @@ -0,0 +1,109 @@ +/* + * 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.wm; + +import android.os.Handler; +import android.os.RemoteException; +import android.view.IDisplayWindowRotationCallback; +import android.view.IDisplayWindowRotationController; +import android.view.IWindowManager; +import android.view.WindowContainerTransaction; + +import java.util.ArrayList; + +/** + * This module deals with display rotations coming from WM. When WM starts a rotation: after it has + * frozen the screen, it will call into this class. This will then call all registered local + * controllers and give them a chance to queue up task changes to be applied synchronously with that + * rotation. + */ +public class DisplayChangeController { + + private final Handler mHandler; + private final IWindowManager mWmService; + + private final ArrayList<OnDisplayChangingListener> mRotationListener = + new ArrayList<>(); + private final ArrayList<OnDisplayChangingListener> mTmpListeners = new ArrayList<>(); + + private final IDisplayWindowRotationController mDisplayRotationController = + new IDisplayWindowRotationController.Stub() { + @Override + public void onRotateDisplay(int displayId, final int fromRotation, + final int toRotation, IDisplayWindowRotationCallback callback) { + mHandler.post(() -> { + WindowContainerTransaction t = new WindowContainerTransaction(); + synchronized (mRotationListener) { + mTmpListeners.clear(); + // Make a local copy in case the handlers add/remove themselves. + mTmpListeners.addAll(mRotationListener); + } + for (OnDisplayChangingListener c : mTmpListeners) { + c.onRotateDisplay(displayId, fromRotation, toRotation, t); + } + try { + callback.continueRotateDisplay(toRotation, t); + } catch (RemoteException e) { + } + }); + } + }; + + public DisplayChangeController(Handler mainHandler, IWindowManager wmService) { + mHandler = mainHandler; + mWmService = wmService; + try { + mWmService.setDisplayWindowRotationController(mDisplayRotationController); + } catch (RemoteException e) { + throw new RuntimeException("Unable to register rotation controller"); + } + } + + /** + * Adds a display rotation controller. + */ + public void addRotationListener(OnDisplayChangingListener listener) { + synchronized (mRotationListener) { + mRotationListener.add(listener); + } + } + + /** + * Removes a display rotation controller. + */ + public void removeRotationListener(OnDisplayChangingListener listener) { + synchronized (mRotationListener) { + mRotationListener.remove(listener); + } + } + + /** + * Give a listener a chance to queue up configuration changes to execute as part of a + * display rotation. The contents of {@link #onRotateDisplay} must run synchronously. + */ + public interface OnDisplayChangingListener { + /** + * Called before the display is rotated. Contents of this method must run synchronously. + * @param displayId Id of display that is rotating. + * @param fromRotation starting rotation of the display. + * @param toRotation target rotation of the display (after rotating). + * @param t A task transaction to populate. + */ + void onRotateDisplay(int displayId, int fromRotation, int toRotation, + WindowContainerTransaction t); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java index 951d6dd4c3a3..bc24ad0118e7 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java @@ -26,12 +26,10 @@ import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.IDisplayWindowListener; -import android.view.IDisplayWindowRotationCallback; -import android.view.IDisplayWindowRotationController; import android.view.IWindowManager; -import android.view.WindowContainerTransaction; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.wm.DisplayChangeController.OnDisplayChangingListener; import java.util.ArrayList; @@ -45,42 +43,16 @@ import javax.inject.Singleton; * rotation. */ @Singleton -public class DisplayWindowController { - private static final String TAG = "DisplayWindowController"; +public class DisplayController { + private static final String TAG = "DisplayController"; private final Handler mHandler; private final Context mContext; private final IWindowManager mWmService; - - private final ArrayList<OnDisplayWindowRotationController> mRotationControllers = - new ArrayList<>(); - private final ArrayList<OnDisplayWindowRotationController> mTmpControllers = new ArrayList<>(); + private final DisplayChangeController mChangeController; private final SparseArray<DisplayRecord> mDisplays = new SparseArray<>(); - private final ArrayList<DisplayWindowListener> mDisplayChangedListeners = new ArrayList<>(); - - private final IDisplayWindowRotationController mDisplayRotationController = - new IDisplayWindowRotationController.Stub() { - @Override - public void onRotateDisplay(int displayId, final int fromRotation, - final int toRotation, IDisplayWindowRotationCallback callback) { - mHandler.post(() -> { - WindowContainerTransaction t = new WindowContainerTransaction(); - synchronized (mRotationControllers) { - mTmpControllers.clear(); - // Make a local copy in case the handlers add/remove themselves. - mTmpControllers.addAll(mRotationControllers); - } - for (OnDisplayWindowRotationController c : mTmpControllers) { - c.onRotateDisplay(displayId, fromRotation, toRotation, t); - } - try { - callback.continueRotateDisplay(toRotation, t); - } catch (RemoteException e) { - } - }); - } - }; + private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>(); /** * Get's a display by id from DisplayManager. @@ -160,14 +132,14 @@ public class DisplayWindowController { }; @Inject - public DisplayWindowController(Context context, @Main Handler mainHandler, + public DisplayController(Context context, @Main Handler mainHandler, IWindowManager wmService) { mHandler = mainHandler; mContext = context; mWmService = wmService; + mChangeController = new DisplayChangeController(mHandler, mWmService); try { mWmService.registerDisplayWindowListener(mDisplayContainerListener); - mWmService.setDisplayWindowRotationController(mDisplayRotationController); } catch (RemoteException e) { throw new RuntimeException("Unable to register hierarchy listener"); } @@ -193,7 +165,7 @@ public class DisplayWindowController { * Add a display window-container listener. It will get notified whenever a display's * configuration changes or when displays are added/removed from the WM hierarchy. */ - public void addDisplayWindowListener(DisplayWindowListener listener) { + public void addDisplayWindowListener(OnDisplaysChangedListener listener) { synchronized (mDisplays) { if (mDisplayChangedListeners.contains(listener)) { return; @@ -208,7 +180,7 @@ public class DisplayWindowController { /** * Remove a display window-container listener. */ - public void removeDisplayWindowListener(DisplayWindowListener listener) { + public void removeDisplayWindowListener(OnDisplaysChangedListener listener) { synchronized (mDisplays) { mDisplayChangedListeners.remove(listener); } @@ -217,19 +189,15 @@ public class DisplayWindowController { /** * Adds a display rotation controller. */ - public void addRotationController(OnDisplayWindowRotationController controller) { - synchronized (mRotationControllers) { - mRotationControllers.add(controller); - } + public void addDisplayChangingController(OnDisplayChangingListener controller) { + mChangeController.addRotationListener(controller); } /** * Removes a display rotation controller. */ - public void removeRotationController(OnDisplayWindowRotationController controller) { - synchronized (mRotationControllers) { - mRotationControllers.remove(controller); - } + public void removeDisplayChangingController(OnDisplayChangingListener controller) { + mChangeController.removeRotationListener(controller); } private static class DisplayRecord { @@ -244,36 +212,20 @@ public class DisplayWindowController { * * @see IDisplayWindowListener */ - public interface DisplayWindowListener { + public interface OnDisplaysChangedListener { /** * Called when a display has been added to the WM hierarchy. */ - void onDisplayAdded(int displayId); + default void onDisplayAdded(int displayId) {} /** * Called when a display's window-container configuration changes. */ - void onDisplayConfigurationChanged(int displayId, Configuration newConfig); + default void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {} /** * Called when a display is removed. */ - void onDisplayRemoved(int displayId); - } - - /** - * Give a controller a chance to queue up configuration changes to execute as part of a - * display rotation. The contents of {@link #onRotateDisplay} must run synchronously. - */ - public interface OnDisplayWindowRotationController { - /** - * Called before the display is rotated. Contents of this method must run synchronously. - * @param displayId Id of display that is rotating. - * @param fromRotation starting rotation of the display. - * @param toRotation target rotation of the display (after rotating). - * @param t A task transaction to populate. - */ - void onRotateDisplay(int displayId, int fromRotation, int toRotation, - WindowContainerTransaction t); + default void onDisplayRemoved(int displayId) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index d413308d4573..7dad05df8f2c 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -45,7 +45,7 @@ import javax.inject.Singleton; * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode. */ @Singleton -public class DisplayImeController implements DisplayWindowController.DisplayWindowListener { +public class DisplayImeController implements DisplayController.OnDisplaysChangedListener { private static final String TAG = "DisplayImeController"; static final int ANIMATION_DURATION_SHOW_MS = 275; @@ -63,7 +63,7 @@ public class DisplayImeController implements DisplayWindowController.DisplayWind final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); @Inject - DisplayImeController(SystemWindows syswin, DisplayWindowController displayController, + public DisplayImeController(SystemWindows syswin, DisplayController displayController, @Main Handler mainHandler) { mHandler = mainHandler; mSystemWindows = syswin; @@ -315,19 +315,20 @@ public class DisplayImeController implements DisplayWindowController.DisplayWind /** * Called when the IME position is starting to animate. */ - void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, boolean showing, - SurfaceControl.Transaction t); + default void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, + boolean showing, SurfaceControl.Transaction t) {} /** * Called when the ime position changed. This is expected to be a synchronous call on the * animation thread. Operations can be added to the transaction to be applied in sync. */ - void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t); + default void onImePositionChanged(int displayId, int imeTop, + SurfaceControl.Transaction t) {} /** * Called when the IME position is done animating. */ - void onImeEndPositioning(int displayId, int imeTop, boolean showing, - SurfaceControl.Transaction t); + default void onImeEndPositioning(int displayId, int imeTop, boolean showing, + SurfaceControl.Transaction t) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java index 5aba013a7fb8..044a2a6cc4b6 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java +++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java @@ -64,11 +64,11 @@ public class SystemWindows { final HashMap<View, SurfaceControlViewHost> mViewRoots = new HashMap<>(); Context mContext; IWindowSession mSession; - DisplayWindowController mDisplayController; + DisplayController mDisplayController; IWindowManager mWmService; - private final DisplayWindowController.DisplayWindowListener mDisplayListener = - new DisplayWindowController.DisplayWindowListener() { + private final DisplayController.OnDisplaysChangedListener mDisplayListener = + new DisplayController.OnDisplaysChangedListener() { @Override public void onDisplayAdded(int displayId) { } @@ -86,7 +86,7 @@ public class SystemWindows { }; @Inject - public SystemWindows(Context context, DisplayWindowController displayController, + public SystemWindows(Context context, DisplayController displayController, IWindowManager wmService) { mContext = context; mWmService = wmService; @@ -239,12 +239,12 @@ public class SystemWindows { Rect outVisibleInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize) { + Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight, viewVisibility, flags, frameNumber, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, cutout, mergedConfiguration, outSurfaceControl, outInsetsState, - outSurfaceSize); + outSurfaceSize, outBLASTSurfaceControl); if (res != 0) { return res; } 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 2c9058afd7ab..5cfb4b39b16b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -279,10 +279,10 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); // Make it look like dismissed notif - mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).setShowInShade(false); + mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).setSuppressNotification(true); // Now remove the bubble mBubbleController.removeBubble( @@ -323,7 +323,7 @@ public class BubbleControllerTest extends SysuiTestCase { // We should have bubbles & their notifs should not be suppressed assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); assertFalse(mNotificationShadeWindowController.getBubbleExpanded()); // Expand the stack @@ -335,7 +335,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Make sure the notif is suppressed assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); // Collapse mBubbleController.collapseStack(); @@ -355,9 +355,9 @@ public class BubbleControllerTest extends SysuiTestCase { // We should have bubbles & their notifs should not be suppressed assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow2.getEntry().getKey())); + mRow2.getEntry())); // Expand BubbleStackView stackView = mBubbleController.getStackView(); @@ -368,13 +368,14 @@ public class BubbleControllerTest extends SysuiTestCase { // Last added is the one that is expanded assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow2.getEntry().getKey())); + mRow2.getEntry())); // Switch which bubble is expanded mBubbleController.selectBubble(mRow.getEntry().getKey()); - mBubbleController.expandStack(); + mBubbleData.setExpanded(true); + assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); // collapse for previous bubble verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); @@ -395,7 +396,7 @@ public class BubbleControllerTest extends SysuiTestCase { // We should have bubbles & their notifs should not be suppressed assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); mTestableLooper.processAllMessages(); assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); @@ -407,7 +408,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Notif is suppressed after expansion assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); // Notif shouldn't show dot after expansion assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); } @@ -421,7 +422,7 @@ public class BubbleControllerTest extends SysuiTestCase { // We should have bubbles & their notifs should not be suppressed assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); mTestableLooper.processAllMessages(); assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); @@ -433,7 +434,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Notif is suppressed after expansion assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); // Notif shouldn't show dot after expansion assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); @@ -443,7 +444,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Nothing should have changed // Notif is suppressed after expansion assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); // Notif shouldn't show dot after expansion assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); } @@ -467,7 +468,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Last added is the one that is expanded assertEquals(mRow2.getEntry(), stackView.getExpandedBubble().getEntry()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow2.getEntry().getKey())); + mRow2.getEntry())); // Dismiss currently expanded mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(), @@ -536,7 +537,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Notif should be suppressed because we were foreground assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); // Dot + flyout is hidden because notif is suppressed assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout()); @@ -551,7 +552,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Should not be suppressed assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); // Should show dot assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); @@ -562,7 +563,7 @@ public class BubbleControllerTest extends SysuiTestCase { // Notif should be suppressed assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); // Dot + flyout is hidden because notif is suppressed assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showFlyout()); @@ -571,7 +572,6 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */); } - @Test public void testExpandStackAndSelectBubble_removedFirst() { final String key = mRow.getEntry().getKey(); @@ -590,7 +590,7 @@ public class BubbleControllerTest extends SysuiTestCase { public void testMarkNewNotificationAsShowInShade() { mEntryListener.onNotificationAdded(mRow.getEntry()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); mTestableLooper.processAllMessages(); assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot()); @@ -663,7 +663,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( mRow.getEntry().getKey(), REASON_CANCEL_ALL); @@ -672,7 +672,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(intercepted); // Should update show in shade state assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); verify(mNotificationEntryManager, never()).performRemoveNotification( any(), anyInt()); @@ -686,7 +686,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested( mRow.getEntry().getKey(), REASON_CANCEL); @@ -695,7 +695,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(intercepted); // Should update show in shade state assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); verify(mNotificationEntryManager, never()).performRemoveNotification( any(), anyInt()); @@ -709,7 +709,7 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( - mRow.getEntry().getKey())); + mRow.getEntry())); // Dismiss the bubble mBubbleController.removeBubble( @@ -724,6 +724,52 @@ public class BubbleControllerTest extends SysuiTestCase { assertFalse(intercepted); } + @Test + public void testNotifyShadeSuppressionChange_notificationDismiss() { + BubbleController.NotificationSuppressionChangedListener listener = + mock(BubbleController.NotificationSuppressionChangedListener.class); + mBubbleData.setSuppressionChangedListener(listener); + + mEntryListener.onNotificationAdded(mRow.getEntry()); + + assertTrue(mBubbleController.hasBubbles()); + assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade( + mRow.getEntry())); + + mRemoveInterceptor.onNotificationRemoveRequested(mRow.getEntry().getKey(), REASON_CANCEL); + + // 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.onNotificationAdded(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())); + } + static class TestableBubbleController extends BubbleController { // Let's assume surfaces can be synchronized immediately. TestableBubbleController(Context context, 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 1a2e23796c78..c9f5b40e8f9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -104,6 +104,9 @@ public class BubbleDataTest extends SysuiTestCase { @Captor private ArgumentCaptor<BubbleData.Update> mUpdateCaptor; + @Mock + private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; + @Before public void setUp() throws Exception { mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency); @@ -121,20 +124,20 @@ public class BubbleDataTest extends SysuiTestCase { modifyRanking(mEntryInterruptive) .setVisuallyInterruptive(true) .build(); - mBubbleInterruptive = new Bubble(mEntryInterruptive); + mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener); ExpandableNotificationRow row = mNotificationTestHelper.createBubble(); mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d"); mEntryDismissed.setRow(row); - mBubbleDismissed = new Bubble(mEntryDismissed); + mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener); - mBubbleA1 = new Bubble(mEntryA1); - mBubbleA2 = new Bubble(mEntryA2); - mBubbleA3 = new Bubble(mEntryA3); - mBubbleB1 = new Bubble(mEntryB1); - mBubbleB2 = new Bubble(mEntryB2); - mBubbleB3 = new Bubble(mEntryB3); - mBubbleC1 = new Bubble(mEntryC1); + mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener); + mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener); + mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener); + mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener); + mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener); + mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener); + mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener); mBubbleData = new BubbleData(getContext()); @@ -237,9 +240,8 @@ public class BubbleDataTest extends SysuiTestCase { true /* showInShade */); verifyUpdateReceived(); - // Make it look like user swiped away row - mEntryDismissed.getRow().dismiss(false /* refocusOnDismiss */); - assertThat(mBubbleData.getBubbleWithKey(mBubbleDismissed.getKey()).showInShade()).isFalse(); + // Suppress the notif / make it look dismissed + mBubbleDismissed.setSuppressNotification(true); mBubbleData.notificationEntryUpdated(mBubbleDismissed, false /* suppressFlyout */, true /* showInShade */); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java index 02f721ce58a7..7f67657e1109 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java @@ -16,11 +16,19 @@ package com.android.systemui.bubbles; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.app.Notification; +import android.app.PendingIntent; +import android.content.Intent; +import android.graphics.drawable.Icon; import android.os.Bundle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -30,6 +38,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.tests.R; import org.junit.Before; import org.junit.Test; @@ -46,6 +55,10 @@ public class BubbleTest extends SysuiTestCase { private NotificationEntry mEntry; private Bundle mExtras; + private Bubble mBubble; + + @Mock + private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; @Before public void setUp() { @@ -57,6 +70,15 @@ public class BubbleTest extends SysuiTestCase { mEntry = new NotificationEntryBuilder() .setNotification(mNotif) .build(); + + mBubble = new Bubble(mEntry, mSuppressionListener); + + Intent target = new Intent(mContext, BubblesTestActivity.class); + Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder() + .createIntentBubble(PendingIntent.getActivity(mContext, 0, target, 0), + Icon.createWithResource(mContext, R.drawable.android)) + .build(); + mEntry.setBubbleMetadata(metadata); } @Test @@ -123,4 +145,24 @@ public class BubbleTest extends SysuiTestCase { BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).senderName); } + + @Test + public void testSuppressionListener_change_notified() { + assertThat(mBubble.showInShade()).isTrue(); + + mBubble.setSuppressNotification(true); + + assertThat(mBubble.showInShade()).isFalse(); + + verify(mSuppressionListener).onBubbleNotificationSuppressionChange(mBubble); + } + + @Test + public void testSuppressionListener_noChange_doesntNotify() { + assertThat(mBubble.showInShade()).isTrue(); + + mBubble.setSuppressNotification(false); + + verify(mSuppressionListener, never()).onBubbleNotificationSuppressionChange(any()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java new file mode 100644 index 000000000000..bc3ce8baddee --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.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 com.android.systemui.pip; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableResources; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.IPinnedStackController; + +import androidx.test.filters.SmallTest; + +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.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests against {@link PipBoundsHandler}, including but not limited to: + * - default/movement bounds + * - save/restore PiP position on application lifecycle + * - save/restore PiP position on screen rotation + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class PipBoundsHandlerTest extends SysuiTestCase { + private static final int ROUNDING_ERROR_MARGIN = 10; + + private PipBoundsHandler mPipBoundsHandler; + private DisplayInfo mDefaultDisplayInfo; + private Rect mDefaultDisplayRect; + + @Mock + private IPinnedStackController mPinnedStackController; + + @Before + public void setUp() throws Exception { + mPipBoundsHandler = new PipBoundsHandler(mContext); + MockitoAnnotations.initMocks(this); + initializeMockResources(); + + mPipBoundsHandler.onDisplayInfoChanged(mDefaultDisplayInfo); + mPipBoundsHandler.setPinnedStackController(mPinnedStackController); + } + + private void initializeMockResources() { + final TestableResources res = mContext.getOrCreateTestableResources(); + res.addOverride( + com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio, 1f); + res.addOverride( + com.android.internal.R.integer.config_defaultPictureInPictureGravity, + Gravity.END | Gravity.BOTTOM); + res.addOverride( + com.android.internal.R.dimen.default_minimal_size_pip_resizable_task, 100); + res.addOverride( + com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets, + "16x16"); + res.addOverride( + com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio, 0.5f); + res.addOverride( + com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio, 2f); + + mDefaultDisplayInfo = new DisplayInfo(); + mDefaultDisplayInfo.displayId = 1; + mDefaultDisplayInfo.logicalWidth = 1000; + mDefaultDisplayInfo.logicalHeight = 1500; + mDefaultDisplayRect = new Rect(0, 0, + mDefaultDisplayInfo.logicalWidth, mDefaultDisplayInfo.logicalHeight); + } + + @Test + public void setShelfHeight_offsetBounds() throws Exception { + final ArgumentCaptor<Rect> destinationBounds = ArgumentCaptor.forClass(Rect.class); + final int shelfHeight = 100; + + mPipBoundsHandler.onPrepareAnimation(null, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), isNull(), anyInt()); + final Rect lastPosition = destinationBounds.getValue(); + // Reset the pinned stack controller since we will do another verify later on + reset(mPinnedStackController); + + mPipBoundsHandler.setShelfHeight(true, shelfHeight); + mPipBoundsHandler.onPrepareAnimation(null, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), isNull(), anyInt()); + lastPosition.offset(0, -shelfHeight); + assertBoundsWithMargin("PiP bounds offset by shelf height", + lastPosition, destinationBounds.getValue()); + } + + @Test + public void onImeVisibilityChanged_offsetBounds() throws Exception { + final ArgumentCaptor<Rect> destinationBounds = ArgumentCaptor.forClass(Rect.class); + final int imeHeight = 100; + + mPipBoundsHandler.onPrepareAnimation(null, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), isNull(), anyInt()); + final Rect lastPosition = destinationBounds.getValue(); + // Reset the pinned stack controller since we will do another verify later on + reset(mPinnedStackController); + + mPipBoundsHandler.onImeVisibilityChanged(true, imeHeight); + mPipBoundsHandler.onPrepareAnimation(null, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), isNull(), anyInt()); + lastPosition.offset(0, -imeHeight); + assertBoundsWithMargin("PiP bounds offset by IME height", + lastPosition, destinationBounds.getValue()); + } + + @Test + public void onPrepareAnimation_startAnimation() throws Exception { + final Rect sourceRectHint = new Rect(100, 100, 200, 200); + final ArgumentCaptor<Rect> destinationBounds = ArgumentCaptor.forClass(Rect.class); + + mPipBoundsHandler.onPrepareAnimation(sourceRectHint, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), eq(sourceRectHint), anyInt()); + final Rect capturedDestinationBounds = destinationBounds.getValue(); + assertFalse("Destination bounds is not empty", + capturedDestinationBounds.isEmpty()); + assertBoundsWithMargin("Destination bounds within Display", + mDefaultDisplayRect, capturedDestinationBounds); + } + + @Test + public void onSaveReentryBounds_restoreLastPosition() throws Exception { + final ComponentName componentName = new ComponentName(mContext, "component1"); + final ArgumentCaptor<Rect> destinationBounds = ArgumentCaptor.forClass(Rect.class); + + mPipBoundsHandler.onPrepareAnimation(null, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), isNull(), anyInt()); + final Rect lastPosition = destinationBounds.getValue(); + lastPosition.offset(0, -100); + mPipBoundsHandler.onSaveReentryBounds(componentName, lastPosition); + // Reset the pinned stack controller since we will do another verify later on + reset(mPinnedStackController); + + mPipBoundsHandler.onPrepareAnimation(null, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), isNull(), anyInt()); + assertBoundsWithMargin("Last position is restored", + lastPosition, destinationBounds.getValue()); + } + + @Test + public void onResetReentryBounds_componentMatch_useDefaultBounds() throws Exception { + final ComponentName componentName = new ComponentName(mContext, "component1"); + final ArgumentCaptor<Rect> destinationBounds = ArgumentCaptor.forClass(Rect.class); + + mPipBoundsHandler.onPrepareAnimation(null, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), isNull(), anyInt()); + final Rect defaultBounds = new Rect(destinationBounds.getValue()); + final Rect newBounds = new Rect(defaultBounds); + newBounds.offset(0, -100); + mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds); + // Reset the pinned stack controller since we will do another verify later on + reset(mPinnedStackController); + + mPipBoundsHandler.onResetReentryBounds(componentName); + mPipBoundsHandler.onPrepareAnimation(null, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), isNull(), anyInt()); + final Rect actualBounds = destinationBounds.getValue(); + assertBoundsWithMargin("Use default bounds", defaultBounds, actualBounds); + } + + @Test + public void onResetReentryBounds_componentMismatch_restoreLastPosition() throws Exception { + final ComponentName componentName = new ComponentName(mContext, "component1"); + final ArgumentCaptor<Rect> destinationBounds = ArgumentCaptor.forClass(Rect.class); + + mPipBoundsHandler.onPrepareAnimation(null, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), isNull(), anyInt()); + final Rect defaultBounds = new Rect(destinationBounds.getValue()); + final Rect newBounds = new Rect(defaultBounds); + newBounds.offset(0, -100); + mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds); + // Reset the pinned stack controller since we will do another verify later on + reset(mPinnedStackController); + + mPipBoundsHandler.onResetReentryBounds(new ComponentName(mContext, "component2")); + mPipBoundsHandler.onPrepareAnimation(null, 1f, null); + + verify(mPinnedStackController).startAnimation( + destinationBounds.capture(), isNull(), anyInt()); + final Rect actualBounds = destinationBounds.getValue(); + assertBoundsWithMargin("Last position is restored", newBounds, actualBounds); + } + + private void assertBoundsWithMargin(String msg, Rect expected, Rect actual) { + expected.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN); + assertTrue(msg, expected.contains(actual)); + } +} 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 0251f2d0f542..9a7e97b5d55a 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 @@ -62,6 +62,7 @@ import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoa import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener; 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.NotifLifetimeExtender; import com.android.systemui.util.Assert; @@ -85,6 +86,7 @@ import java.util.Map; public class NotifCollectionTest extends SysuiTestCase { @Mock private IStatusBarService mStatusBarService; + @Mock private NotifCollectionLogger mLogger; @Mock private GroupCoalescer mGroupCoalescer; @Spy private RecordingCollectionListener mCollectionListener; @Mock private CollectionReadyForBuildListener mBuildListener; @@ -111,9 +113,11 @@ public class NotifCollectionTest extends SysuiTestCase { when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(true); when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(true); - mCollection = new NotifCollection(mStatusBarService, + mCollection = new NotifCollection( + mStatusBarService, mock(DumpController.class), - mFeatureFlags); + mFeatureFlags, + mLogger); mCollection.attach(mGroupCoalescer); mCollection.addCollectionListener(mCollectionListener); mCollection.setBuildListener(mBuildListener); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index e915be37705b..18f133f09811 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -45,12 +45,12 @@ import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.O import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener; +import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderLogger; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator; 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.CollectionReadyForBuildListener; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.util.Assert; import com.android.systemui.util.time.FakeSystemClock; @@ -81,7 +81,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { private ShadeListBuilder mListBuilder; private FakeSystemClock mSystemClock = new FakeSystemClock(); - @Mock private NotifLog mNotifLog; + @Mock private ShadeListBuilderLogger mLogger; @Mock private NotifCollection mNotifCollection; @Spy private OnBeforeTransformGroupsListener mOnBeforeTransformGroupsListener; @Spy private OnBeforeSortListener mOnBeforeSortListener; @@ -103,7 +103,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); Assert.sMainLooper = TestableLooper.get(this).getLooper(); - mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog, mock(DumpController.class)); + mListBuilder = new ShadeListBuilder(mSystemClock, mLogger, mock(DumpController.class)); mListBuilder.setOnRenderListListener(mOnRenderListListener); mListBuilder.attach(mNotifCollection); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java index 86c1eb97d186..ac9a57022f53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.collection.coalescer; -import static com.android.internal.util.Preconditions.checkNotNull; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.clearInvocations; @@ -25,6 +23,8 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static java.util.Objects.requireNonNull; + import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; @@ -39,7 +39,6 @@ import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.notification.collection.NoManSimulator; import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -63,7 +62,7 @@ public class GroupCoalescerTest extends SysuiTestCase { @Mock private NotificationListener mListenerService; @Mock private GroupCoalescer.BatchableNotificationHandler mListener; - @Mock private NotifLog mLog; + @Mock private GroupCoalescerLogger mLogger; @Captor private ArgumentCaptor<NotificationHandler> mListenerCaptor; @@ -79,14 +78,14 @@ public class GroupCoalescerTest extends SysuiTestCase { new GroupCoalescer( mExecutor, mClock, - mLog, + mLogger, MIN_LINGER_DURATION, MAX_LINGER_DURATION); mCoalescer.setNotificationHandler(mListener); mCoalescer.attach(mListenerService); verify(mListenerService).addNotificationHandler(mListenerCaptor.capture()); - NotificationHandler serviceListener = checkNotNull(mListenerCaptor.getValue()); + NotificationHandler serviceListener = requireNonNull(mListenerCaptor.getValue()); mNoMan.addListener(serviceListener); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index 5fc40ccb6c8a..20a089f97f51 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -58,7 +58,6 @@ import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.util.Slog; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; @@ -601,6 +600,34 @@ public class NotificationConversationInfoTest extends SysuiTestCase { } @Test + public void testBubble_noChannelChange() throws Exception { + mNotificationInfo.bindNotification( + mShortcutManager, + mLauncherApps, + mMockPackageManager, + mMockINotificationManager, + mVisualStabilityManager, + TEST_PACKAGE_NAME, + mNotificationChannel, + mBubbleEntry, + null, + null, + null, + true); + + assertFalse(mBubbleEntry.isBubble()); + assertTrue(mNotificationChannel.canBubble()); + + // Promote it + mNotificationInfo.findViewById(R.id.bubble).performClick(); + mTestableLooper.processAllMessages(); + + verify(mBubbleController, times(1)).onUserCreatedBubbleFromNotification(mBubbleEntry); + verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( + anyString(), anyInt(), any()); + } + + @Test public void testFavorite_favorite() throws Exception { mNotificationInfo.bindNotification( mShortcutManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java new file mode 100644 index 000000000000..be43e19cfc70 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java @@ -0,0 +1,90 @@ +/* + * 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.phone; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; + +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) +@TestableLooper.RunWithLooper +public class NotificationIconAreaControllerTest extends SysuiTestCase { + + @Mock + private NotificationListener mListener; + @Mock + StatusBar mStatusBar; + @Mock + StatusBarStateController mStatusBarStateController; + @Mock + NotificationWakeUpCoordinator mWakeUpCoordinator; + @Mock + KeyguardBypassController mKeyguardBypassController; + @Mock + NotificationMediaManager mNotificationMediaManager; + @Mock + DozeParameters mDozeParameters; + @Mock + NotificationShadeWindowView mNotificationShadeWindowView; + private NotificationIconAreaController mController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + when(mStatusBar.getNotificationShadeWindowView()).thenReturn(mNotificationShadeWindowView); + when(mNotificationShadeWindowView.findViewById(anyInt())).thenReturn( + mock(NotificationIconContainer.class)); + + mController = new NotificationIconAreaController(mContext, mStatusBar, + mStatusBarStateController, mWakeUpCoordinator, mKeyguardBypassController, + mNotificationMediaManager, mListener, mDozeParameters); + } + + @Test + public void testNotificationIcons_settingHideIcons() { + mController.mSettingsListener.onStatusBarIconsBehaviorChanged(true); + + assertFalse(mController.shouldShouldLowPriorityIcons()); + } + + @Test + public void testNotificationIcons_settingShowIcons() { + mController.mSettingsListener.onStatusBarIconsBehaviorChanged(false); + + assertTrue(mController.shouldShouldLowPriorityIcons()); + } +} diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java b/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java index 7c41377985d3..9fda1257b4c9 100644 --- a/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java +++ b/packages/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java @@ -28,4 +28,9 @@ public abstract class DhcpServerCallbacks extends IDhcpServerCallbacks.Stub { public int getInterfaceVersion() { return IDhcpServerCallbacks.VERSION; } + + @Override + public String getInterfaceHash() { + return IDhcpServerCallbacks.HASH; + } } diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index 190d25098644..f39e7af43ee6 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -301,6 +301,11 @@ public class IpServer extends StateMachine { public int getInterfaceVersion() { return this.VERSION; } + + @Override + public String getInterfaceHash() { + return this.HASH; + } } private class DhcpServerCallbacksImpl extends DhcpServerCallbacks { diff --git a/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java b/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java index 3218c0b387bb..b1ffdb01f5f3 100644 --- a/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java +++ b/packages/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java @@ -67,4 +67,9 @@ public class BaseNetdUnsolicitedEventListener extends INetdUnsolicitedEventListe public int getInterfaceVersion() { return INetdUnsolicitedEventListener.VERSION; } + + @Override + public String getInterfaceHash() { + return INetdUnsolicitedEventListener.HASH; + } } 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 b5660ae5d41a..1fe162c86408 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -16,6 +16,12 @@ package com.android.server.accessibility.gestures; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_3_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; @@ -104,6 +110,20 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mGestures.add(new Swipe(context, UP, DOWN, GESTURE_SWIPE_UP_AND_DOWN, this)); mGestures.add(new Swipe(context, UP, LEFT, GESTURE_SWIPE_UP_AND_LEFT, this)); mGestures.add(new Swipe(context, UP, RIGHT, GESTURE_SWIPE_UP_AND_RIGHT, this)); + // Two-finger taps. + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP, this)); + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this)); + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP, this)); + // Three-finger taps. + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 3, 1, GESTURE_3_FINGER_SINGLE_TAP, this)); + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 3, 2, GESTURE_3_FINGER_DOUBLE_TAP, this)); + mMultiFingerGestures.add( + new MultiFingerMultiTap(mContext, 3, 3, GESTURE_3_FINGER_TRIPLE_TAP, this)); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java new file mode 100644 index 000000000000..20def6154977 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerMultiTap.java @@ -0,0 +1,291 @@ +/* + * 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.accessibility.gestures; + +import android.content.Context; +import android.graphics.PointF; +import android.os.Handler; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * This class matches multi-finger multi-tap gestures. The number of fingers and the number of taps + * for each instance is specified in the constructor. + */ +class MultiFingerMultiTap extends GestureMatcher { + + // The target number of taps. + final int mTargetTapCount; + // The target number of fingers. + final int mTargetFingerCount; + // The acceptable distance between two taps of a finger. + private int mDoubleTapSlop; + // The acceptable distance the pointer can move and still count as a tap. + private int mTouchSlop; + // A tap counts when target number of fingers are down and up once. + private int mCompletedTapCount; + // A flag set to true when target number of fingers have touched down at once before. + // Used to indicate what next finger action should be. Down when false and lift when true. + private boolean mIsTargetFingerCountReached = false; + // Store initial down points for slop checking and update when next down if is inside slop. + private PointF[] mBases; + // The points in bases that already have slop checked when onDown or onPointerDown. + // It prevents excluded points matched multiple times by other pointers from next check. + private ArrayList<PointF> mExcludedPointsForDownSlopChecked; + + /** + * @throws IllegalArgumentException if <code>fingers<code/> is less than 2 + * or <code>taps<code/> is not positive. + */ + MultiFingerMultiTap(Context context, int fingers, int taps, int gestureId, + GestureMatcher.StateChangeListener listener) { + super(gestureId, new Handler(context.getMainLooper()), listener); + Preconditions.checkArgument(fingers >= 2); + Preconditions.checkArgumentPositive(taps, "Tap count must greater than 0."); + mTargetTapCount = taps; + mTargetFingerCount = fingers; + mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + + mBases = new PointF[mTargetFingerCount]; + for (int i = 0; i < mBases.length; i++) { + mBases[i] = new PointF(); + } + mExcludedPointsForDownSlopChecked = new ArrayList<>(mTargetFingerCount); + clear(); + } + + @Override + protected void clear() { + mCompletedTapCount = 0; + mIsTargetFingerCountReached = false; + for (int i = 0; i < mBases.length; i++) { + mBases[i].set(Float.NaN, Float.NaN); + } + mExcludedPointsForDownSlopChecked.clear(); + super.clear(); + } + + @Override + protected void onDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // Before the matcher state transit to completed, + // Cancel when an additional down arrived after reaching the target number of taps. + if (mCompletedTapCount == mTargetTapCount) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + cancelAfterTapTimeout(event, rawEvent, policyFlags); + + if (mCompletedTapCount == 0) { + initBaseLocation(rawEvent); + return; + } + // As fingers go up and down, their pointer ids will not be the same. + // Therefore we require that a given finger be in slop range of any one + // of the fingers from the previous tap. + final PointF nearest = findNearestPoint(rawEvent, mDoubleTapSlop, true); + if (nearest != null) { + // Update pointer location to nearest one as a new base for next slop check. + final int index = event.getActionIndex(); + nearest.set(event.getX(index), event.getY(index)); + } else { + cancelGesture(event, rawEvent, policyFlags); + } + } + + @Override + protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + cancelAfterDoubleTapTimeout(event, rawEvent, policyFlags); + + final PointF nearest = findNearestPoint(rawEvent, mTouchSlop, false); + if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) + && null != nearest) { + // Increase current tap count when the user have all fingers lifted + // within the tap timeout since the target number of fingers are down. + if (mIsTargetFingerCountReached) { + mCompletedTapCount++; + mIsTargetFingerCountReached = false; + mExcludedPointsForDownSlopChecked.clear(); + } + + // Start gesture detection here to avoid the conflict to 2nd finger double tap + // that never actually started gesture detection. + if (mCompletedTapCount == 1) { + startGesture(event, rawEvent, policyFlags); + } + if (mCompletedTapCount == mTargetTapCount) { + // Done. + completeAfterDoubleTapTimeout(event, rawEvent, policyFlags); + } + } else { + // Either too many taps or nonsensical event stream. + cancelGesture(event, rawEvent, policyFlags); + } + } + + @Override + protected void onMove(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // Outside the touch slop + if (null == findNearestPoint(rawEvent, mTouchSlop, false)) { + cancelGesture(event, rawEvent, policyFlags); + } + } + + @Override + protected void onPointerDown(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // Reset timeout to ease the use for some people + // with certain impairments to get all their fingers down. + cancelAfterTapTimeout(event, rawEvent, policyFlags); + final int currentFingerCount = event.getPointerCount(); + // Accept down only before target number of fingers are down + // or the finger count is not more than target. + if ((currentFingerCount > mTargetFingerCount) || mIsTargetFingerCountReached) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + + final PointF nearest; + if (mCompletedTapCount == 0) { + nearest = initBaseLocation(rawEvent); + } else { + nearest = findNearestPoint(rawEvent, mDoubleTapSlop, true); + } + if ((getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) + && nearest != null) { + // The user have all fingers down within the tap timeout since first finger down, + // setting the timeout for fingers to be lifted. + if (currentFingerCount == mTargetFingerCount) { + mIsTargetFingerCountReached = true; + } + // Update pointer location to nearest one as a new base for next slop check. + final int index = event.getActionIndex(); + nearest.set(event.getX(index), event.getY(index)); + } else { + cancelGesture(event, rawEvent, policyFlags); + } + } + + @Override + protected void onPointerUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // Accept up only after target number of fingers are down. + if (!mIsTargetFingerCountReached) { + cancelGesture(event, rawEvent, policyFlags); + return; + } + + if (getState() == STATE_GESTURE_STARTED || getState() == STATE_CLEAR) { + // Needs more fingers lifted within the tap timeout + // after reaching the target number of fingers are down. + } else { + cancelGesture(event, rawEvent, policyFlags); + } + } + + @Override + public String getGestureName() { + final StringBuilder builder = new StringBuilder(); + builder.append(mTargetFingerCount).append("-Finger "); + if (mTargetTapCount == 1) { + builder.append("Single"); + } else if (mTargetTapCount == 2) { + builder.append("Double"); + } else if (mTargetTapCount == 3) { + builder.append("Triple"); + } else if (mTargetTapCount > 3) { + builder.append(mTargetTapCount); + } + return builder.append(" Tap").toString(); + } + + private PointF initBaseLocation(MotionEvent event) { + final int index = event.getActionIndex(); + final int baseIndex = event.getPointerCount() - 1; + final PointF p = mBases[baseIndex]; + if (Float.isNaN(p.x) && Float.isNaN(p.y)) { + p.set(event.getX(index), event.getY(index)); + } + return p; + } + + /** + * Find the nearest location to the given event in the bases. + * If no one found, it could be not inside {@code slop}, filtered or empty bases. + * When {@code filterMatched} is true, if the location of given event matches one of the points + * in {@link #mExcludedPointsForDownSlopChecked} it would be ignored. Otherwise, the location + * will be added to {@link #mExcludedPointsForDownSlopChecked}. + * + * @param event to find nearest point in bases. + * @param slop to check to the given location of the event. + * @param filterMatched true to exclude points already matched other pointers. + * @return the point in bases closed to the location of the given event. + */ + private PointF findNearestPoint(MotionEvent event, float slop, boolean filterMatched) { + float moveDelta = Float.MAX_VALUE; + PointF nearest = null; + for (int i = 0; i < mBases.length; i++) { + final PointF p = mBases[i]; + if (Float.isNaN(p.x) && Float.isNaN(p.y)) { + continue; + } + if (filterMatched && mExcludedPointsForDownSlopChecked.contains(p)) { + continue; + } + final int index = event.getActionIndex(); + final float dX = p.x - event.getX(index); + final float dY = p.y - event.getY(index); + if (dX == 0 && dY == 0) { + if (filterMatched) { + mExcludedPointsForDownSlopChecked.add(p); + } + return p; + } + final float delta = (float) Math.hypot(dX, dY); + if (moveDelta > delta) { + moveDelta = delta; + nearest = p; + } + } + if (moveDelta < slop) { + if (filterMatched) { + mExcludedPointsForDownSlopChecked.add(nearest); + } + return nearest; + } + return null; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(super.toString()); + if (getState() != STATE_GESTURE_CANCELED) { + builder.append(", CompletedTapCount: "); + builder.append(mCompletedTapCount); + builder.append(", IsTargetFingerCountReached: "); + builder.append(mIsTargetFingerCountReached); + builder.append(", Bases: "); + builder.append(Arrays.toString(mBases)); + builder.append(", ExcludedPointsForDownSlopChecked: "); + builder.append(mExcludedPointsForDownSlopChecked.toString()); + } + return builder.toString(); + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java index eb38b53dc52a..8a566fcdb48a 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/SecondFingerMultiTap.java @@ -124,6 +124,12 @@ class SecondFingerMultiTap extends GestureMatcher { } @Override + protected void onUp(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // Cancel early when possible, or it will take precedence over two-finger double tap. + cancelGesture(event, rawEvent, policyFlags); + } + + @Override public String getGestureName() { switch (mTargetTaps) { case 2: diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index 16484df9f328..f90d936a5f4d 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -76,6 +76,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -697,11 +698,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { mStageName = new File(backupManagerService.getDataDir(), packageName + ".stage"); mNewStateName = new File(mStateDir, packageName + ".new"); - // don't stage the 'android' package where the wallpaper data lives. this is - // an optimization: we know there's no widget data hosted/published by that - // package, and this way we avoid doing a spurious copy of MB-sized wallpaper - // data following the download. - boolean staging = !packageName.equals(PLATFORM_PACKAGE_NAME); + boolean staging = shouldStageBackupData(packageName); ParcelFileDescriptor stage; File downloadFile = (staging) ? mStageName : mBackupDataName; boolean startedAgentRestore = false; @@ -768,8 +765,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { startedAgentRestore = true; mAgent.doRestoreWithExcludedKeys(mBackupData, appVersionCode, mNewState, mEphemeralOpToken, backupManagerService.getBackupManagerBinder(), - mExcludedKeys.containsKey(packageName) - ? new ArrayList<>(mExcludedKeys.get(packageName)) : null); + new ArrayList<>(getExcludedKeysForPackage(packageName))); } catch (Exception e) { Slog.e(TAG, "Unable to call app for restore: " + packageName, e); EventLog.writeEvent(EventLogTags.RESTORE_AGENT_FAILURE, @@ -785,9 +781,24 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { } @VisibleForTesting + boolean shouldStageBackupData(String packageName) { + // Backup data is staged for 2 reasons: + // 1. We might need to exclude keys from the data before passing it to the agent + // 2. Widget metadata needs to be separated from the rest to be handled separately + // But 'android' package doesn't contain widget metadata so we want to skip staging for it + // when there are no keys to be excluded either. + return !packageName.equals(PLATFORM_PACKAGE_NAME) || + !getExcludedKeysForPackage(PLATFORM_PACKAGE_NAME).isEmpty(); + } + + private Set<String> getExcludedKeysForPackage(String packageName) { + return mExcludedKeys.getOrDefault(packageName, Collections.emptySet()); + } + + @VisibleForTesting void filterExcludedKeys(String packageName, BackupDataInput in, BackupDataOutput out) throws Exception { - Set<String> excludedKeysForPackage = mExcludedKeys.get(packageName); + Set<String> excludedKeysForPackage = getExcludedKeysForPackage(packageName); byte[] buffer = new byte[8192]; // will grow when needed while (in.readNextHeader()) { diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 0b7029b1a71d..5602d1a851e8 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -92,6 +92,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -670,7 +671,7 @@ public final class ContentCaptureManagerService extends service.onDataSharedLocked(request, new DataShareCallbackDelegate(request, clientCancellationSignal, - clientAdapter)); + clientAdapter, ContentCaptureManagerService.this)); } } @@ -918,20 +919,22 @@ public final class ContentCaptureManagerService extends } } - // TODO(b/148265162): DataShareCallbackDelegate should be a static class keeping week references - // to the needed info - private class DataShareCallbackDelegate extends IDataShareCallback.Stub { + private static class DataShareCallbackDelegate extends IDataShareCallback.Stub { @NonNull private final DataShareRequest mDataShareRequest; - @NonNull private final ICancellationSignal mClientCancellationSignal; - @NonNull private final IDataShareWriteAdapter mClientAdapter; + @NonNull + private final WeakReference<ICancellationSignal> mClientCancellationSignalReference; + @NonNull private final WeakReference<IDataShareWriteAdapter> mClientAdapterReference; + @NonNull private final WeakReference<ContentCaptureManagerService> mParentServiceReference; DataShareCallbackDelegate(@NonNull DataShareRequest dataShareRequest, @NonNull ICancellationSignal clientCancellationSignal, - @NonNull IDataShareWriteAdapter clientAdapter) { + @NonNull IDataShareWriteAdapter clientAdapter, + ContentCaptureManagerService parentService) { mDataShareRequest = dataShareRequest; - mClientCancellationSignal = clientCancellationSignal; - mClientAdapter = clientAdapter; + mClientCancellationSignalReference = new WeakReference<>(clientCancellationSignal); + mClientAdapterReference = new WeakReference<>(clientAdapter); + mParentServiceReference = new WeakReference<>(parentService); } @Override @@ -939,41 +942,52 @@ public final class ContentCaptureManagerService extends IDataShareReadAdapter serviceAdapter) throws RemoteException { Slog.i(TAG, "Data share request accepted by Content Capture service"); + final ContentCaptureManagerService parentService = mParentServiceReference.get(); + final ICancellationSignal clientCancellationSignal = + mClientCancellationSignalReference.get(); + final IDataShareWriteAdapter clientAdapter = mClientAdapterReference.get(); + if (parentService == null || clientCancellationSignal == null + || clientAdapter == null) { + Slog.w(TAG, "Can't fulfill accept() request, because remote objects have been " + + "GC'ed"); + return; + } + Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe(); if (clientPipe == null) { - mClientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN); + clientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN); serviceAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN); return; } - ParcelFileDescriptor source_in = clientPipe.second; - ParcelFileDescriptor sink_in = clientPipe.first; + ParcelFileDescriptor sourceIn = clientPipe.second; + ParcelFileDescriptor sinkIn = clientPipe.first; Pair<ParcelFileDescriptor, ParcelFileDescriptor> servicePipe = createPipe(); if (servicePipe == null) { - bestEffortCloseFileDescriptors(source_in, sink_in); + bestEffortCloseFileDescriptors(sourceIn, sinkIn); - mClientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN); + clientAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN); serviceAdapter.error(DataShareWriteAdapter.ERROR_UNKNOWN); return; } - ParcelFileDescriptor source_out = servicePipe.second; - ParcelFileDescriptor sink_out = servicePipe.first; + ParcelFileDescriptor sourceOut = servicePipe.second; + ParcelFileDescriptor sinkOut = servicePipe.first; ICancellationSignal cancellationSignalTransport = CancellationSignal.createTransport(); - mPackagesWithShareRequests.add(mDataShareRequest.getPackageName()); + parentService.mPackagesWithShareRequests.add(mDataShareRequest.getPackageName()); - mClientAdapter.write(source_in); - serviceAdapter.start(sink_out, cancellationSignalTransport); + clientAdapter.write(sourceIn); + serviceAdapter.start(sinkOut, cancellationSignalTransport); CancellationSignal cancellationSignal = CancellationSignal.fromTransport(cancellationSignalTransport); cancellationSignal.setOnCancelListener(() -> { try { - mClientCancellationSignal.cancel(); + clientCancellationSignal.cancel(); } catch (RemoteException e) { Slog.e(TAG, "Failed to propagate cancel operation to the caller", e); } @@ -982,13 +996,13 @@ public final class ContentCaptureManagerService extends // File descriptor received by the client app will be a copy of the current one. Close // the one that belongs to the system server, so there's only 1 open left for the // current pipe. - bestEffortCloseFileDescriptor(source_in); + bestEffortCloseFileDescriptor(sourceIn); - mDataShareExecutor.execute(() -> { + parentService.mDataShareExecutor.execute(() -> { try (InputStream fis = - new ParcelFileDescriptor.AutoCloseInputStream(sink_in); + new ParcelFileDescriptor.AutoCloseInputStream(sinkIn); OutputStream fos = - new ParcelFileDescriptor.AutoCloseOutputStream(source_out)) { + new ParcelFileDescriptor.AutoCloseOutputStream(sourceOut)) { byte[] byteBuffer = new byte[DATA_SHARE_BYTE_BUFFER_LENGTH]; while (true) { @@ -1005,52 +1019,86 @@ public final class ContentCaptureManagerService extends } }); - mHandler.postDelayed(() -> { - synchronized (mLock) { - mPackagesWithShareRequests.remove(mDataShareRequest.getPackageName()); - - // Interaction finished successfully <=> all data has been written to Content - // Capture Service. If it hasn't been read successfully, service would be able - // to signal through the cancellation signal. - boolean finishedSuccessfully = !sink_in.getFileDescriptor().valid() - && !source_out.getFileDescriptor().valid(); - - if (finishedSuccessfully) { - Slog.i(TAG, "Content capture data sharing session terminated " - + "successfully for package '" - + mDataShareRequest.getPackageName() - + "'"); - } else { - Slog.i(TAG, "Reached the timeout of Content Capture data sharing session " - + "for package '" - + mDataShareRequest.getPackageName() - + "', terminating the pipe."); - } - - // Ensure all the descriptors are closed after the session. - bestEffortCloseFileDescriptors(source_in, sink_in, source_out, sink_out); - - if (!finishedSuccessfully) { - try { - mClientCancellationSignal.cancel(); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to cancel() the client operation", e); - } - try { - serviceCancellationSignal.cancel(); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to cancel() the service operation", e); - } - } - } - }, MAX_DATA_SHARE_FILE_DESCRIPTORS_TTL_MS); + parentService.mHandler.postDelayed(() -> + enforceDataSharingTtl( + sourceIn, + sinkIn, + sourceOut, + sinkOut, + new WeakReference<>(serviceCancellationSignal)), + MAX_DATA_SHARE_FILE_DESCRIPTORS_TTL_MS); } @Override public void reject() throws RemoteException { Slog.i(TAG, "Data share request rejected by Content Capture service"); - mClientAdapter.rejected(); + IDataShareWriteAdapter clientAdapter = mClientAdapterReference.get(); + if (clientAdapter == null) { + Slog.w(TAG, "Can't fulfill reject() request, because remote objects have been " + + "GC'ed"); + return; + } + + clientAdapter.rejected(); + } + + private void enforceDataSharingTtl(ParcelFileDescriptor sourceIn, + ParcelFileDescriptor sinkIn, + ParcelFileDescriptor sourceOut, + ParcelFileDescriptor sinkOut, + WeakReference<ICancellationSignal> serviceCancellationSignalReference) { + + final ContentCaptureManagerService parentService = mParentServiceReference.get(); + final ICancellationSignal clientCancellationSignal = + mClientCancellationSignalReference.get(); + final ICancellationSignal serviceCancellationSignal = + serviceCancellationSignalReference.get(); + if (parentService == null || clientCancellationSignal == null + || serviceCancellationSignal == null) { + Slog.w(TAG, "Can't enforce data sharing TTL, because remote objects have been " + + "GC'ed"); + return; + } + + synchronized (parentService.mLock) { + parentService.mPackagesWithShareRequests + .remove(mDataShareRequest.getPackageName()); + + // Interaction finished successfully <=> all data has been written to Content + // Capture Service. If it hasn't been read successfully, service would be able + // to signal through the cancellation signal. + boolean finishedSuccessfully = !sinkIn.getFileDescriptor().valid() + && !sourceOut.getFileDescriptor().valid(); + + if (finishedSuccessfully) { + Slog.i(TAG, "Content capture data sharing session terminated " + + "successfully for package '" + + mDataShareRequest.getPackageName() + + "'"); + } else { + Slog.i(TAG, "Reached the timeout of Content Capture data sharing session " + + "for package '" + + mDataShareRequest.getPackageName() + + "', terminating the pipe."); + } + + // Ensure all the descriptors are closed after the session. + bestEffortCloseFileDescriptors(sourceIn, sinkIn, sourceOut, sinkOut); + + if (!finishedSuccessfully) { + try { + clientCancellationSignal.cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to cancel() the client operation", e); + } + try { + serviceCancellationSignal.cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to cancel() the service operation", e); + } + } + } } private Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index dd33566322e2..d933e9d8e9da 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -2962,6 +2962,11 @@ public class ConnectivityService extends IConnectivityManager.Stub public int getInterfaceVersion() { return this.VERSION; } + + @Override + public String getInterfaceHash() { + return this.HASH; + } } private boolean networkRequiresPrivateDnsValidation(NetworkAgentInfo nai) { diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 4a093cde8e71..f1f2d2abea3f 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -860,13 +860,7 @@ public class LocationManagerService extends ILocationManager.Stub { pw.increaseIndent(); - boolean enabled = isEnabled(); - pw.println("enabled=" + enabled); - if (!enabled) { - pw.println("allowed=" + mProvider.getState().allowed); - } - - pw.println("properties=" + mProvider.getState().properties); + pw.println("enabled=" + isEnabled()); } mProvider.dump(fd, pw, args); diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 05d83606b2d0..9d4c78383183 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -735,6 +735,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub { public int getInterfaceVersion() { return INetdUnsolicitedEventListener.VERSION; } + + @Override + public String getInterfaceHash() { + return INetdUnsolicitedEventListener.HASH; + } } // diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index 131a22b07c7b..bacffb6140ed 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -213,10 +213,10 @@ public class RescueParty { } /** - * Get the current rescue level. + * Get the next rescue level. This indicates the next level of mitigation that may be taken. */ - private static int getRescueLevel() { - return MathUtils.constrain(SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE), + private static int getNextRescueLevel() { + return MathUtils.constrain(SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1, LEVEL_NONE, LEVEL_FACTORY_RESET); } @@ -225,9 +225,7 @@ public class RescueParty { * probably want to call {@link #executeRescueLevel(Context, String)}. */ private static void incrementRescueLevel(int triggerUid) { - final int level = MathUtils.constrain( - SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1, - LEVEL_NONE, LEVEL_FACTORY_RESET); + final int level = getNextRescueLevel(); SystemProperties.set(PROP_RESCUE_LEVEL, Integer.toString(level)); EventLogTags.writeRescueLevel(level, triggerUid); @@ -397,10 +395,7 @@ public class RescueParty { @FailureReasons int failureReason) { if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { - int rescueLevel = MathUtils.constrain( - SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1, - LEVEL_NONE, LEVEL_FACTORY_RESET); - return mapRescueLevelToUserImpact(rescueLevel); + return mapRescueLevelToUserImpact(getNextRescueLevel()); } else { return PackageHealthObserverImpact.USER_IMPACT_NONE; } @@ -449,7 +444,7 @@ public class RescueParty { if (isDisabled()) { return PackageHealthObserverImpact.USER_IMPACT_NONE; } - return mapRescueLevelToUserImpact(getRescueLevel()); + return mapRescueLevelToUserImpact(getNextRescueLevel()); } @Override diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index 8564cb456ba6..14cd3a5d5c0d 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -167,7 +167,7 @@ public class ServiceWatcher implements ServiceConnection { @Override public String toString() { - return component + "@" + version + "[u" + userId + "]"; + return component.toShortString() + "@" + version + "[u" + userId + "]"; } } @@ -341,7 +341,7 @@ public class ServiceWatcher implements ServiceConnection { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); if (D) { - Log.i(TAG, getLogPrefix() + " connected to " + component); + Log.i(TAG, getLogPrefix() + " connected to " + component.toShortString()); } mBinder = binder; @@ -351,11 +351,22 @@ public class ServiceWatcher implements ServiceConnection { } @Override + public void onBindingDied(ComponentName component) { + Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); + + if (D) { + Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died"); + } + + onBestServiceChanged(true); + } + + @Override public final void onServiceDisconnected(ComponentName component) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); if (D) { - Log.i(TAG, getLogPrefix() + " disconnected from " + component); + Log.i(TAG, getLogPrefix() + " disconnected from " + component.toShortString()); } mBinder = null; @@ -383,8 +394,8 @@ public class ServiceWatcher implements ServiceConnection { } /** - * Runs the given function asynchronously if currently connected. Suppresses any RemoteException - * thrown during execution. + * Runs the given function asynchronously if and only if currently connected. Suppresses any + * RemoteException thrown during execution. */ public final void runOnBinder(BinderRunner runner) { runOnHandler(() -> { @@ -473,4 +484,9 @@ public class ServiceWatcher implements ServiceConnection { private String getLogPrefix() { return "[" + mIntent.getAction() + "]"; } + + @Override + public String toString() { + return mServiceInfo.toString(); + } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 4b4ce348385a..a04f25b0d20c 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1701,15 +1701,8 @@ class StorageManagerService extends IStorageManager.Stub if (mIsFuseEnabled != settingsFuseFlag) { Slog.i(TAG, "Toggling persist.sys.fuse to " + settingsFuseFlag); SystemProperties.set(PROP_FUSE, Boolean.toString(settingsFuseFlag)); - - PowerManager powerManager = mContext.getSystemService(PowerManager.class); - if (powerManager.isRebootingUserspaceSupported()) { - // Perform userspace reboot to kick policy into place - powerManager.reboot(PowerManager.REBOOT_USERSPACE); - } else { - // Perform hard reboot to kick policy into place - powerManager.reboot("fuse_prop"); - } + // Perform hard reboot to kick policy into place + mContext.getSystemService(PowerManager.class).reboot("fuse_prop"); } } @@ -4329,6 +4322,19 @@ class StorageManagerService extends IStorageManager.Stub public void onAppOpsChanged(int code, int uid, @Nullable String packageName, int mode) { + if (code == OP_REQUEST_INSTALL_PACKAGES && mIsFuseEnabled) { + // When using FUSE, we basically have no other choice but to kill the app + // after the app op is either granted or rejected. + final IActivityManager am = ActivityManager.getService(); + try { + am.killApplication(packageName, + UserHandle.getAppId(uid), + UserHandle.USER_ALL, AppOpsManager.opToName(code) + " changed."); + } catch (RemoteException e) { + } + + return; + } if (mode == MODE_ALLOWED && (code == OP_READ_EXTERNAL_STORAGE || code == OP_WRITE_EXTERNAL_STORAGE || code == OP_REQUEST_INSTALL_PACKAGES)) { diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 0b723c997a83..059eb6ad724c 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -36,6 +36,10 @@ } ], "file_patterns": ["NotificationManagerService\\.java"] + }, + { + "name": "FuseDaemonHostTest", + "file_patterns": ["StorageManagerService\\.java"] } ] } diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 5a56a9fa5367..6bc090a4f7ba 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -16,6 +16,8 @@ package com.android.server; +import static android.os.VibrationEffect.Composition.PrimitiveEffect; + import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppOpsManager; @@ -73,8 +75,10 @@ import com.android.internal.util.DumpUtils; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.LinkedList; +import java.util.List; public class VibratorService extends IVibratorService.Stub implements InputManager.InputDeviceListener { @@ -128,10 +132,11 @@ public class VibratorService extends IVibratorService.Stub private final boolean mAllowPriorityVibrationsInLowPowerMode; private final boolean mSupportsAmplitudeControl; private final boolean mSupportsExternalControl; + private final List<Integer> mSupportedEffects; private final long mCapabilities; private final int mDefaultVibrationAmplitude; private final SparseArray<VibrationEffect> mFallbackEffects; - private final SparseArray<Integer> mProcStatesCache = new SparseArray(); + private final SparseArray<Integer> mProcStatesCache = new SparseArray<>(); private final WorkSource mTmpWorkSource = new WorkSource(); private final Handler mH = new Handler(); private final Object mLock = new Object(); @@ -150,7 +155,7 @@ public class VibratorService extends IVibratorService.Stub // mInputDeviceVibrators lock should be acquired after mLock, if both are // to be acquired - private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>(); + private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<>(); private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators @@ -171,7 +176,10 @@ public class VibratorService extends IVibratorService.Stub static native void vibratorOff(); static native boolean vibratorSupportsAmplitudeControl(); static native void vibratorSetAmplitude(int amplitude); + static native int[] vibratorGetSupportedEffects(); static native long vibratorPerformEffect(long effect, long strength, Vibration vibration); + static native void vibratorPerformComposedEffect( + VibrationEffect.Composition.PrimitiveEffect[] effect, Vibration vibration); static native boolean vibratorSupportsExternalControl(); static native void vibratorSetExternalControl(boolean enabled); static native long vibratorGetCapabilities(); @@ -238,6 +246,8 @@ public class VibratorService extends IVibratorService.Stub } } + // Called by native + @SuppressWarnings("unused") private void onComplete() { synchronized (mLock) { if (this == mCurrentVibration) { @@ -346,10 +356,11 @@ public class VibratorService extends IVibratorService.Stub mSupportsAmplitudeControl = vibratorSupportsAmplitudeControl(); mSupportsExternalControl = vibratorSupportsExternalControl(); + mSupportedEffects = asList(vibratorGetSupportedEffects()); mCapabilities = vibratorGetCapabilities(); mContext = context; - PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + PowerManager pm = context.getSystemService(PowerManager.class); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); mWakeLock.setReferenceCounted(true); @@ -510,12 +521,47 @@ public class VibratorService extends IVibratorService.Stub } @Override // Binder call + public boolean[] areEffectsSupported(int[] effectIds) { + // Return null to indicate that the HAL doesn't actually tell us what effects are + // supported. + if (mSupportedEffects == null) { + return null; + } + boolean[] supported = new boolean[effectIds.length]; + for (int i = 0; i < effectIds.length; i++) { + supported[i] = mSupportedEffects.contains(effectIds[i]); + } + return supported; + } + + @Override // Binder call + public boolean[] arePrimitivesSupported(int[] primitiveIds) { + boolean[] supported = new boolean[primitiveIds.length]; + if (hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { + Arrays.fill(supported, true); + } + return supported; + } + + + private static List<Integer> asList(int... vals) { + if (vals == null) { + return null; + } + List<Integer> l = new ArrayList<>(vals.length); + for (int val : vals) { + l.add(val); + } + return l; + } + + @Override // Binder call public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect, VibrationAttributes attrs) { if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) { throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission"); } - if ((mCapabilities & IVibrator.CAP_ALWAYS_ON_CONTROL) == 0) { + if (!hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { Slog.e(TAG, "Always-on effects not supported."); return false; } @@ -817,6 +863,14 @@ public class VibratorService extends IVibratorService.Stub if (timeout > 0) { mH.postDelayed(mVibrationEndRunnable, timeout); } + } else if (vib.effect instanceof VibrationEffect.Composed) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); + doVibratorComposedEffectLocked(vib); + // FIXME: We rely on the completion callback here, but I don't think we require that + // devices which support composition also support the completion callback. If we + // ever get a device that supports the former but not the latter, then we have no + // real way of knowing how long a given effect should last. + mH.postDelayed(mVibrationEndRunnable, 10000); } else { Slog.e(TAG, "Unknown vibration type, ignoring"); } @@ -1148,7 +1202,7 @@ public class VibratorService extends IVibratorService.Stub } } else { // Note: ordering is important here! Many haptic drivers will reset their - // amplitude when enabled, so we always have to enable frst, then set the + // amplitude when enabled, so we always have to enable first, then set the // amplitude. vibratorOn(millis); doVibratorSetAmplitude(amplitude); @@ -1201,7 +1255,7 @@ public class VibratorService extends IVibratorService.Stub long duration = vibratorPerformEffect(prebaked.getId(), prebaked.getEffectStrength(), vib); long timeout = duration; - if ((mCapabilities & IVibrator.CAP_PERFORM_CALLBACK) != 0) { + if (hasCapability(IVibrator.CAP_PERFORM_CALLBACK)) { timeout *= ASYNC_TIMEOUT_MULTIPLIER; } if (timeout > 0) { @@ -1229,6 +1283,41 @@ public class VibratorService extends IVibratorService.Stub } } + @GuardedBy("mLock") + private void doVibratorComposedEffectLocked(Vibration vib) { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorComposedEffectLocked"); + + try { + final VibrationEffect.Composed composed = (VibrationEffect.Composed) vib.effect; + final boolean usingInputDeviceVibrators; + synchronized (mInputDeviceVibrators) { + usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty(); + } + // Input devices don't support composed effect, so skip trying it with them. + if (usingInputDeviceVibrators) { + return; + } + + if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { + return; + } + + PrimitiveEffect[] primitiveEffects = + composed.getPrimitiveEffects().toArray(new PrimitiveEffect[0]); + vibratorPerformComposedEffect(primitiveEffects, vib); + + // Composed effects don't actually give us an estimated duration, so we just guess here. + noteVibratorOnLocked(vib.uid, 10 * primitiveEffects.length); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + + } + + private boolean hasCapability(long capability) { + return (mCapabilities & capability) == capability; + } + private VibrationEffect getFallbackEffect(int effectId) { return mFallbackEffects.get(effectId); } @@ -1424,7 +1513,7 @@ public class VibratorService extends IVibratorService.Stub long[] timings, int[] amplitudes, int startIndex, int repeatIndex) { int i = startIndex; long timing = 0; - while(amplitudes[i] != 0) { + while (amplitudes[i] != 0) { timing += timings[i++]; if (i >= timings.length) { if (repeatIndex >= 0) { @@ -1475,18 +1564,14 @@ public class VibratorService extends IVibratorService.Stub } else { pw.println("null"); } - pw.print(" mCurrentExternalVibration="); - if (mCurrentExternalVibration != null) { - pw.println(mCurrentExternalVibration.toString()); - } else { - pw.println("null"); - } + pw.print(" mCurrentExternalVibration=" + mCurrentExternalVibration); pw.println(" mVibratorUnderExternalControl=" + mVibratorUnderExternalControl); pw.println(" mLowPowerMode=" + mLowPowerMode); pw.println(" mHapticFeedbackIntensity=" + mHapticFeedbackIntensity); pw.println(" mNotificationIntensity=" + mNotificationIntensity); pw.println(" mRingIntensity=" + mRingIntensity); - pw.println(""); + pw.println(" mSupportedEffects=" + mSupportedEffects); + pw.println(); pw.println(" Previous ring vibrations:"); for (VibrationInfo info : mPreviousRingVibrations) { pw.print(" "); @@ -1495,34 +1580,29 @@ public class VibratorService extends IVibratorService.Stub pw.println(" Previous notification vibrations:"); for (VibrationInfo info : mPreviousNotificationVibrations) { - pw.print(" "); - pw.println(info.toString()); + pw.println(" " + info); } pw.println(" Previous alarm vibrations:"); for (VibrationInfo info : mPreviousAlarmVibrations) { - pw.print(" "); - pw.println(info.toString()); + pw.println(" " + info); } pw.println(" Previous vibrations:"); for (VibrationInfo info : mPreviousVibrations) { - pw.print(" "); - pw.println(info.toString()); + pw.println(" " + info); } pw.println(" Previous external vibrations:"); for (ExternalVibration vib : mPreviousExternalVibrations) { - pw.print(" "); - pw.println(vib.toString()); + pw.println(" " + vib); } } } @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, - String[] args, ShellCallback callback, ResultReceiver resultReceiver) - throws RemoteException { + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 0a91f9af1cae..ffeea3d12781 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -287,6 +287,7 @@ public final class ActiveServices { static final int MSG_BG_START_TIMEOUT = 1; static final int MSG_UPDATE_FOREGROUND_APPS = 2; + static final int MSG_ENSURE_NOT_START_BG = 3; ServiceMap(Looper looper, int userId) { super(looper); @@ -304,6 +305,11 @@ public final class ActiveServices { case MSG_UPDATE_FOREGROUND_APPS: { updateForegroundApps(this); } break; + case MSG_ENSURE_NOT_START_BG: { + synchronized (mAm) { + rescheduleDelayedStartsLocked(); + } + } break; } } @@ -311,7 +317,9 @@ public final class ActiveServices { if (mStartingBackground.remove(r)) { if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "No longer background starting: " + r); - rescheduleDelayedStartsLocked(); + removeMessages(MSG_ENSURE_NOT_START_BG); + Message msg = obtainMessage(MSG_ENSURE_NOT_START_BG); + sendMessage(msg); } if (mDelayedStartList.remove(r)) { if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "No longer delaying start: " + r); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8b547c6bda42..4c5f705433d6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7786,7 +7786,10 @@ public class ActivityManagerService extends IActivityManager.Stub * * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/ * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java + * + * @deprecated -- use getProviderMimeTypeAsync. */ + @Deprecated public String getProviderMimeType(Uri uri, int userId) { enforceNotIsolatedCaller("getProviderMimeType"); final String name = uri.getAuthority(); @@ -7847,6 +7850,43 @@ public class ActivityManagerService extends IActivityManager.Stub return null; } + /** + * Allows apps to retrieve the MIME type of a URI. + * If an app is in the same user as the ContentProvider, or if it is allowed to interact across + * users, then it does not need permission to access the ContentProvider. + * Either way, it needs cross-user uri grants. + */ + @Override + public void getProviderMimeTypeAsync(Uri uri, int userId, RemoteCallback resultCallback) { + enforceNotIsolatedCaller("getProviderMimeTypeAsync"); + final String name = uri.getAuthority(); + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + final int safeUserId = mUserController.unsafeConvertIncomingUser(userId); + final long ident = canClearIdentity(callingPid, callingUid, userId) + ? Binder.clearCallingIdentity() : 0; + try { + final ContentProviderHolder holder = getContentProviderExternalUnchecked(name, null, + callingUid, "*getmimetype*", safeUserId); + if (holder != null) { + holder.provider.getTypeAsync(uri, new RemoteCallback(result -> { + final long identity = Binder.clearCallingIdentity(); + try { + removeContentProviderExternalUnchecked(name, null, safeUserId); + } finally { + Binder.restoreCallingIdentity(identity); + } + resultCallback.sendResult(result); + })); + } + } catch (RemoteException e) { + Log.w(TAG, "Content provider dead retrieving " + uri, e); + resultCallback.sendResult(Bundle.EMPTY); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + int checkContentProviderUriPermission(Uri uri, int userId, int callingUid, int modeFlags) { final String name = uri.getAuthority(); final long ident = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index e9d64913a354..3bd7d5c0d075 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -17,8 +17,8 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL; -import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT; +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.ActivityManager.PROCESS_CAPABILITY_NONE; @@ -36,9 +36,7 @@ import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; -import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; -import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; import static android.os.Process.SCHED_OTHER; import static android.os.Process.THREAD_GROUP_BACKGROUND; import static android.os.Process.THREAD_GROUP_DEFAULT; @@ -78,7 +76,6 @@ import android.compat.annotation.Disabled; import android.compat.annotation.EnabledAfter; import android.content.Context; import android.content.pm.ServiceInfo; -import android.os.Build; import android.os.Debug; import android.os.Handler; import android.os.IBinder; @@ -151,6 +148,9 @@ public final class OomAdjuster { @Disabled static final long CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID = 136219221L; + //TODO: remove this when development is done. + private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31; + /** * For some direct access we need to power manager. */ @@ -1477,13 +1477,24 @@ public final class OomAdjuster { } } - if (s.isForeground && s.mAllowWhileInUsePermissionInFgs) { + if (s.isForeground) { final int fgsType = s.foregroundServiceType; - capabilityFromFGS |= - (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) - != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; - capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_CAMERA - | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; + if (s.mAllowWhileInUsePermissionInFgs) { + capabilityFromFGS |= + (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) + != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; + } else { + //The FGS has the location capability, but due to FGS BG start restriction it + //lost the capability, use temp location capability to mark this case. + //TODO: remove this block when development is done. + capabilityFromFGS |= + (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) + != 0 ? TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; + } + if (s.mAllowWhileInUsePermissionInFgs) { + capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_CAMERA + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; + } } ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections(); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 954be20122b4..73ca39b22de1 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1574,7 +1574,7 @@ public final class ProcessList { } if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) { // For DownloadProviders and MTP: To grant access to /sdcard/Android/ - gidList.add(Process.SDCARD_RW_GID); + gidList.add(UserHandle.getUid(UserHandle.getUserId(uid), Process.SDCARD_RW_GID)); } if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) { // For the FUSE daemon: To grant access to the lower filesystem. diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 62596de7b9a6..1d714a2c99c7 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -213,6 +213,9 @@ public class AppOpsService extends IAppOpsService.Stub { private static final int MAX_UNFORWARED_OPS = 10; private static final int MAX_UNUSED_POOLED_OBJECTS = 3; + //TODO: remove this when development is done. + private static final int TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31; + Context mContext; final AtomicFile mFile; final Handler mHandler; @@ -480,8 +483,15 @@ public class AppOpsService extends IAppOpsService.Stub { case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: if ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) { return AppOpsManager.MODE_ALLOWED; - } else { + } else if ((capability + & TEMP_PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) { + // The FGS has the location capability, but due to FGS BG start + // restriction it lost the capability, use temp location capability + // to mark this case. + // TODO change to MODE_IGNORED when enforcing the feature. maybeShowWhileInUseDebugToast(op, mode); + return AppOpsManager.MODE_ALLOWED; + } else { return AppOpsManager.MODE_IGNORED; } case OP_CAMERA: @@ -586,7 +596,7 @@ public class AppOpsService extends IAppOpsService.Stub { return; } final long now = System.currentTimeMillis(); - if (lastTimeShowDebugToast == 0 || now - lastTimeShowDebugToast > 600000) { + if (lastTimeShowDebugToast == 0 || now - lastTimeShowDebugToast > 3600000) { lastTimeShowDebugToast = now; mHandler.sendMessage(PooledLambda.obtainMessage( ActivityManagerInternal::showWhileInUseDebugToast, diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index bb8b12e86e16..4d5af9ac5d5c 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -16,6 +16,11 @@ 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.app.ActivityManager; import android.app.IActivityManager; import android.content.Context; @@ -68,12 +73,14 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void reportChange(long changeId, ApplicationInfo appInfo) { + checkCompatChangeLogPermission(); reportChange(changeId, appInfo.uid, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED); } @Override public void reportChangeByPackageName(long changeId, String packageName, int userId) { + checkCompatChangeLogPermission(); ApplicationInfo appInfo = getApplicationInfo(packageName, userId); if (appInfo == null) { return; @@ -83,11 +90,13 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public void reportChangeByUid(long changeId, int uid) { + checkCompatChangeLogPermission(); reportChange(changeId, uid, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED); } @Override public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { + checkCompatChangeReadPermission(); if (mCompatConfig.isChangeEnabled(changeId, appInfo)) { reportChange(changeId, appInfo.uid, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED); @@ -100,6 +109,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public boolean isChangeEnabledByPackageName(long changeId, String packageName, int userId) { + checkCompatChangeReadPermission(); ApplicationInfo appInfo = getApplicationInfo(packageName, userId); if (appInfo == null) { return true; @@ -109,6 +119,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public boolean isChangeEnabledByUid(long changeId, int uid) { + checkCompatChangeReadPermission(); String[] packages = mContext.getPackageManager().getPackagesForUid(uid); if (packages == null || packages.length == 0) { return true; @@ -141,6 +152,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); } @@ -148,11 +160,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); } @@ -160,12 +174,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; @@ -173,11 +189,13 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) { + checkCompatChangeReadPermission(); return mCompatConfig.getAppConfig(appInfo); } @Override public CompatibilityChangeInfo[] listAllChanges() { + checkCompatChangeReadPermission(); return mCompatConfig.dumpChanges(); } @@ -216,6 +234,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + checkCompatChangeReadPermission(); if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return; mCompatConfig.dumpConfig(pw); } @@ -273,4 +292,25 @@ 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"); + } + } } diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index f2892cc81951..17e2f69e9bf1 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -310,6 +310,11 @@ public class NetdEventListenerService extends INetdEventListener.Stub { return this.VERSION; } + @Override + public String getInterfaceHash() { + return this.HASH; + } + private void addWakeupEvent(WakeupEvent event) { String iface = event.iface; mWakeupEvents.append(event); diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java index d673ec84c47e..5876d433face 100644 --- a/services/core/java/com/android/server/incremental/IncrementalManagerService.java +++ b/services/core/java/com/android/server/incremental/IncrementalManagerService.java @@ -134,7 +134,7 @@ public class IncrementalManagerService extends IIncrementalManager.Stub { // TODO: remove this @Override - public void newFileForDataLoader(int mountId, long inode, byte[] metadata) { + public void newFileForDataLoader(int mountId, byte[] fileId, byte[] metadata) { IDataLoader dataLoader = mDataLoaderManager.getDataLoader(mountId); if (dataLoader == null) { Slog.e(TAG, "Failed to retrieve data loader for ID=" + mountId); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 905a94f57262..15698e9795ec 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3510,7 +3510,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** - * This is kept due to {@link android.annotation.UnsupportedAppUsage} in + * This is kept due to {@code @UnsupportedAppUsage} in * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in * {@link InputMethodService#onCreate()}. * diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 11fe15f0e12b..15dd6464e094 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -260,7 +260,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { "Integrity check result: " + result.getEffect() + " due to " - + result.getRule()); + + result.getMatchedRules()); StatsLog.write( StatsLog.INTEGRITY_CHECK_RESULT_REPORTED, @@ -268,9 +268,9 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { appCert, appInstallMetadata.getVersionCode(), installerPackageName, - getLoggingResponse(result), - isCausedByAppCertRule(result), - isCausedByInstallerRule(result)); + result.getLoggingResponse(), + result.isCausedByAppCertRule(), + result.isCausedByInstallerRule()); mPackageManagerInternal.setIntegrityVerificationResult( verificationId, result.getEffect() == IntegrityCheckResult.Effect.ALLOW @@ -583,26 +583,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { } } - private static int getLoggingResponse(IntegrityCheckResult result) { - if (result.getEffect() == IntegrityCheckResult.Effect.DENY) { - return StatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__REJECTED; - } else if (result.getRule() != null) { - return StatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__FORCE_ALLOWED; - } else { - return StatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__ALLOWED; - } - } - - private static boolean isCausedByAppCertRule(IntegrityCheckResult result) { - // TODO(b/147095027): implement this. - return true; - } - - private static boolean isCausedByInstallerRule(IntegrityCheckResult result) { - // TODO(b/147095027): implement this. - return true; - } - private List<String> getAllowedRuleProviders() { return Arrays.asList(mContext.getResources().getStringArray( R.array.config_integrityRuleProviderPackages)); diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java index 66537ff6105e..9d9430441e07 100644 --- a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java +++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java @@ -25,8 +25,8 @@ import android.content.integrity.Rule; import com.android.server.integrity.model.IntegrityCheckResult; -import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** * A helper class for evaluating rules against app install metadata to find if there are matching @@ -48,29 +48,34 @@ final class RuleEvaluator { @NonNull static IntegrityCheckResult evaluateRules( List<Rule> rules, AppInstallMetadata appInstallMetadata) { - List<Rule> matchedRules = new ArrayList<>(); - for (Rule rule : rules) { - if (rule.getFormula().matches(appInstallMetadata)) { - matchedRules.add(rule); - } + + // Identify the rules that match the {@code appInstallMetadata}. + List<Rule> matchedRules = + rules.stream() + .filter(rule -> rule.getFormula().matches(appInstallMetadata)) + .collect(Collectors.toList()); + + // Identify the matched power allow rules and terminate early if we have any. + List<Rule> matchedPowerAllowRules = + matchedRules.stream() + .filter(rule -> rule.getEffect() == FORCE_ALLOW) + .collect(Collectors.toList()); + + if (!matchedPowerAllowRules.isEmpty()) { + return IntegrityCheckResult.allow(matchedPowerAllowRules); } - boolean denied = false; - Rule denyRule = null; - for (Rule rule : matchedRules) { - switch (rule.getEffect()) { - case DENY: - if (!denied) { - denied = true; - denyRule = rule; - } - break; - case FORCE_ALLOW: - return IntegrityCheckResult.allow(rule); - default: - throw new IllegalArgumentException("Matched an unknown effect rule: " + rule); - } + // Identify the matched deny rules. + List<Rule> matchedDenyRules = + matchedRules.stream() + .filter(rule -> rule.getEffect() == DENY) + .collect(Collectors.toList()); + + if (!matchedDenyRules.isEmpty()) { + return IntegrityCheckResult.deny(matchedDenyRules); } - return denied ? IntegrityCheckResult.deny(denyRule) : IntegrityCheckResult.allow(); + + // When no rules are denied, return default allow result. + return IntegrityCheckResult.allow(); } } diff --git a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java index b3cb31ac8cb1..1b605c7c430c 100644 --- a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java +++ b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java @@ -18,6 +18,10 @@ package com.android.server.integrity.model; import android.annotation.Nullable; import android.content.integrity.Rule; +import android.util.StatsLog; + +import java.util.Collections; +import java.util.List; /** * A class encapsulating the result from the evaluation engine after evaluating rules against app @@ -34,19 +38,19 @@ public final class IntegrityCheckResult { } private final Effect mEffect; - @Nullable private final Rule mRule; + private final List<Rule> mRuleList; - private IntegrityCheckResult(Effect effect, @Nullable Rule rule) { + private IntegrityCheckResult(Effect effect, @Nullable List<Rule> ruleList) { this.mEffect = effect; - this.mRule = rule; + this.mRuleList = ruleList; } public Effect getEffect() { return mEffect; } - public Rule getRule() { - return mRule; + public List<Rule> getMatchedRules() { + return mRuleList; } /** @@ -55,7 +59,7 @@ public final class IntegrityCheckResult { * @return An evaluation outcome with ALLOW effect and no rule. */ public static IntegrityCheckResult allow() { - return new IntegrityCheckResult(Effect.ALLOW, null); + return new IntegrityCheckResult(Effect.ALLOW, Collections.emptyList()); } /** @@ -63,17 +67,43 @@ public final class IntegrityCheckResult { * * @return An evaluation outcome with ALLOW effect and rule causing that effect. */ - public static IntegrityCheckResult allow(Rule rule) { - return new IntegrityCheckResult(Effect.ALLOW, rule); + public static IntegrityCheckResult allow(List<Rule> ruleList) { + return new IntegrityCheckResult(Effect.ALLOW, ruleList); } /** * Create a DENY evaluation outcome. * - * @param rule Rule causing the DENY effect. + * @param ruleList All valid rules that cause the DENY effect. * @return An evaluation outcome with DENY effect and rule causing that effect. */ - public static IntegrityCheckResult deny(Rule rule) { - return new IntegrityCheckResult(Effect.DENY, rule); + public static IntegrityCheckResult deny(List<Rule> ruleList) { + return new IntegrityCheckResult(Effect.DENY, ruleList); + } + + /** + * Returns the in value of the integrity check result for logging purposes. + */ + public int getLoggingResponse() { + if (getEffect() == Effect.DENY) { + return StatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__REJECTED; + } else if (getEffect() == Effect.ALLOW && getMatchedRules().isEmpty()) { + return StatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__ALLOWED; + } else if (getEffect() == Effect.ALLOW && !getMatchedRules().isEmpty()) { + return StatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__FORCE_ALLOWED; + } else { + throw new IllegalStateException("IntegrityCheckResult is not valid."); + } } + + /** Returns true when the {@code mEffect} is caused by an app certificate mismatch. */ + public boolean isCausedByAppCertRule() { + return mRuleList.stream().anyMatch(rule -> rule.getFormula().isAppCertificateFormula()); + } + + /** Returns true when the {@code mEffect} is caused by an installer rule. */ + public boolean isCausedByInstallerRule() { + return mRuleList.stream().anyMatch(rule -> rule.getFormula().isInstallerFormula()); + } + } diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java deleted file mode 100644 index f37ca1efce13..000000000000 --- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java +++ /dev/null @@ -1,220 +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.server.integrity.parser; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.Rule; -import android.util.Xml; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.StringReader; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -/** A helper class to parse rules into the {@link Rule} model from Xml representation. */ -public final class RuleXmlParser implements RuleParser { - - public static final String TAG = "RuleXmlParser"; - - private static final String NAMESPACE = ""; - private static final String RULE_LIST_TAG = "RL"; - private static final String RULE_TAG = "R"; - private static final String COMPOUND_FORMULA_TAG = "OF"; - private static final String ATOMIC_FORMULA_TAG = "AF"; - private static final String EFFECT_ATTRIBUTE = "E"; - private static final String KEY_ATTRIBUTE = "K"; - private static final String OPERATOR_ATTRIBUTE = "O"; - private static final String VALUE_ATTRIBUTE = "V"; - private static final String CONNECTOR_ATTRIBUTE = "C"; - private static final String IS_HASHED_VALUE_ATTRIBUTE = "H"; - - @Override - public List<Rule> parse(byte[] ruleBytes) throws RuleParseException { - try { - XmlPullParser xmlPullParser = Xml.newPullParser(); - xmlPullParser.setInput(new StringReader(new String(ruleBytes, StandardCharsets.UTF_8))); - return parseRules(xmlPullParser); - } catch (Exception e) { - throw new RuleParseException(e.getMessage(), e); - } - } - - @Override - public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges) - throws RuleParseException { - try { - XmlPullParser xmlPullParser = Xml.newPullParser(); - xmlPullParser.setInput( - new RandomAccessInputStream(randomAccessObject), - StandardCharsets.UTF_8.name()); - return parseRules(xmlPullParser); - } catch (Exception e) { - throw new RuleParseException(e.getMessage(), e); - } - } - - private static List<Rule> parseRules(XmlPullParser parser) - throws IOException, XmlPullParserException { - List<Rule> rules = new ArrayList<>(); - - // Skipping the first event type, which is always {@link XmlPullParser.START_DOCUMENT} - parser.next(); - - // Processing the first tag; which should always be a RuleList <RL> tag. - String nodeName = parser.getName(); - // Validating that the XML is starting with a RuleList <RL> tag. - // Note: This is the only breaking validation to run against XML files in the platform. - // All rules inside are assumed to be validated at the server. If a rule is found to be - // corrupt in the XML, it will be skipped to the next rule. - if (!nodeName.equals(RULE_LIST_TAG)) { - throw new RuntimeException( - String.format( - "Rules must start with RuleList <RL> tag. Found: %s at %s", - nodeName, parser.getPositionDescription())); - } - - int eventType; - while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { - nodeName = parser.getName(); - if (eventType != XmlPullParser.START_TAG || !nodeName.equals(RULE_TAG)) { - continue; - } - rules.add(parseRule(parser)); - } - - return rules; - } - - private static Rule parseRule(XmlPullParser parser) throws IOException, XmlPullParserException { - IntegrityFormula formula = null; - int effect = Integer.parseInt(extractAttributeValue(parser, EFFECT_ATTRIBUTE).orElse("-1")); - - int eventType; - while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { - String nodeName = parser.getName(); - - if (eventType == XmlPullParser.END_TAG && parser.getName().equals(RULE_TAG)) { - break; - } - - if (eventType == XmlPullParser.START_TAG) { - switch (nodeName) { - case COMPOUND_FORMULA_TAG: - formula = parseCompoundFormula(parser); - break; - case ATOMIC_FORMULA_TAG: - formula = parseAtomicFormula(parser); - break; - default: - throw new RuntimeException( - String.format("Found unexpected tag: %s", nodeName)); - } - } else { - throw new RuntimeException( - String.format("Found unexpected event type: %d", eventType)); - } - } - - return new Rule(formula, effect); - } - - private static IntegrityFormula parseCompoundFormula(XmlPullParser parser) - throws IOException, XmlPullParserException { - int connector = - Integer.parseInt(extractAttributeValue(parser, CONNECTOR_ATTRIBUTE).orElse("-1")); - List<IntegrityFormula> formulas = new ArrayList<>(); - - int eventType; - while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { - String nodeName = parser.getName(); - - if (eventType == XmlPullParser.END_TAG - && parser.getName().equals(COMPOUND_FORMULA_TAG)) { - break; - } - - if (eventType == XmlPullParser.START_TAG) { - switch (nodeName) { - case ATOMIC_FORMULA_TAG: - formulas.add(parseAtomicFormula(parser)); - break; - case COMPOUND_FORMULA_TAG: - formulas.add(parseCompoundFormula(parser)); - break; - default: - throw new RuntimeException( - String.format("Found unexpected tag: %s", nodeName)); - } - } else { - throw new RuntimeException( - String.format("Found unexpected event type: %d", eventType)); - } - } - - return new CompoundFormula(connector, formulas); - } - - private static IntegrityFormula parseAtomicFormula(XmlPullParser parser) - throws IOException, XmlPullParserException { - int key = Integer.parseInt(extractAttributeValue(parser, KEY_ATTRIBUTE).orElse("-1")); - int operator = - Integer.parseInt(extractAttributeValue(parser, OPERATOR_ATTRIBUTE).orElse("-1")); - String value = extractAttributeValue(parser, VALUE_ATTRIBUTE).orElse(null); - String isHashedValue = - extractAttributeValue(parser, IS_HASHED_VALUE_ATTRIBUTE).orElse(null); - - int eventType; - while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) { - if (eventType == XmlPullParser.END_TAG && parser.getName().equals(ATOMIC_FORMULA_TAG)) { - break; - } - } - return constructAtomicFormulaBasedOnKey(key, operator, value, isHashedValue); - } - - private static IntegrityFormula constructAtomicFormulaBasedOnKey( - @AtomicFormula.Key int key, - @AtomicFormula.Operator int operator, - String value, - String isHashedValue) { - switch (key) { - case AtomicFormula.PACKAGE_NAME: - case AtomicFormula.INSTALLER_NAME: - case AtomicFormula.APP_CERTIFICATE: - case AtomicFormula.INSTALLER_CERTIFICATE: - return new AtomicFormula.StringAtomicFormula( - key, value, Boolean.parseBoolean(isHashedValue)); - case AtomicFormula.PRE_INSTALLED: - return new AtomicFormula.BooleanAtomicFormula(key, Boolean.parseBoolean(value)); - case AtomicFormula.VERSION_CODE: - return new AtomicFormula.LongAtomicFormula(key, operator, Integer.parseInt(value)); - default: - throw new RuntimeException(String.format("Found unexpected key: %d", key)); - } - } - - private static Optional<String> extractAttributeValue(XmlPullParser parser, String attribute) { - return Optional.ofNullable(parser.getAttributeValue(NAMESPACE, attribute)); - } -} diff --git a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java deleted file mode 100644 index 6e1218064096..000000000000 --- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java +++ /dev/null @@ -1,203 +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.server.integrity.serializer; - -import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED; -import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.Rule; -import android.util.Xml; - -import org.xmlpull.v1.XmlSerializer; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -/** A helper class to serialize rules from the {@link Rule} model to Xml representation. */ -public class RuleXmlSerializer implements RuleSerializer { - - public static final String TAG = "RuleXmlSerializer"; - private static final String NAMESPACE = ""; - - private static final String RULE_LIST_TAG = "RL"; - private static final String RULE_TAG = "R"; - private static final String COMPOUND_FORMULA_TAG = "OF"; - private static final String ATOMIC_FORMULA_TAG = "AF"; - private static final String EFFECT_ATTRIBUTE = "E"; - private static final String KEY_ATTRIBUTE = "K"; - private static final String OPERATOR_ATTRIBUTE = "O"; - private static final String VALUE_ATTRIBUTE = "V"; - private static final String CONNECTOR_ATTRIBUTE = "C"; - private static final String IS_HASHED_VALUE_ATTRIBUTE = "H"; - - @Override - public void serialize( - List<Rule> rules, - Optional<Integer> formatVersion, - OutputStream outputStream, - OutputStream indexingOutputStream) - throws RuleSerializeException { - try { - XmlSerializer xmlSerializer = Xml.newSerializer(); - xmlSerializer.setOutput(outputStream, StandardCharsets.UTF_8.name()); - serializeRules(rules, xmlSerializer); - - // TODO(b/145493956): Implement the indexing logic. - } catch (Exception e) { - throw new RuleSerializeException(e.getMessage(), e); - } - } - - @Override - public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion) - throws RuleSerializeException { - try { - XmlSerializer xmlSerializer = Xml.newSerializer(); - StringWriter writer = new StringWriter(); - xmlSerializer.setOutput(writer); - serializeRules(rules, xmlSerializer); - return writer.toString().getBytes(StandardCharsets.UTF_8); - } catch (Exception e) { - throw new RuleSerializeException(e.getMessage(), e); - } - } - - private void serializeRules(List<Rule> rules, XmlSerializer xmlSerializer) - throws RuleSerializeException { - try { - // Determine the indexing groups and the order of the rules within each indexed group. - Map<Integer, Map<String, List<Rule>>> indexedRules = - RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules); - - // Write the XML formatted rules in order. - xmlSerializer.startTag(NAMESPACE, RULE_LIST_TAG); - - serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED), xmlSerializer); - serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED), xmlSerializer); - serializeRuleList(indexedRules.get(NOT_INDEXED), xmlSerializer); - - xmlSerializer.endTag(NAMESPACE, RULE_LIST_TAG); - xmlSerializer.endDocument(); - } catch (Exception e) { - throw new RuleSerializeException(e.getMessage(), e); - } - } - - private void serializeRuleList(Map<String, List<Rule>> rulesMap, XmlSerializer xmlSerializer) - throws IOException { - List<String> sortedKeyList = - rulesMap.keySet().stream().sorted().collect(Collectors.toList()); - for (String key : sortedKeyList) { - for (Rule rule : rulesMap.get(key)) { - serializeRule(rule, xmlSerializer); - } - } - } - - private void serializeRule(Rule rule, XmlSerializer xmlSerializer) throws IOException { - if (rule == null) { - return; - } - xmlSerializer.startTag(NAMESPACE, RULE_TAG); - serializeAttributeValue(EFFECT_ATTRIBUTE, String.valueOf(rule.getEffect()), xmlSerializer); - serializeFormula(rule.getFormula(), xmlSerializer); - xmlSerializer.endTag(NAMESPACE, RULE_TAG); - } - - private void serializeFormula(IntegrityFormula formula, XmlSerializer xmlSerializer) - throws IOException { - if (formula instanceof AtomicFormula) { - serializeAtomicFormula((AtomicFormula) formula, xmlSerializer); - } else if (formula instanceof CompoundFormula) { - serializeCompoundFormula((CompoundFormula) formula, xmlSerializer); - } else { - throw new IllegalArgumentException( - String.format("Invalid formula type: %s", formula.getClass())); - } - } - - private void serializeCompoundFormula( - CompoundFormula compoundFormula, XmlSerializer xmlSerializer) throws IOException { - if (compoundFormula == null) { - return; - } - xmlSerializer.startTag(NAMESPACE, COMPOUND_FORMULA_TAG); - serializeAttributeValue( - CONNECTOR_ATTRIBUTE, String.valueOf(compoundFormula.getConnector()), xmlSerializer); - for (IntegrityFormula formula : compoundFormula.getFormulas()) { - serializeFormula(formula, xmlSerializer); - } - xmlSerializer.endTag(NAMESPACE, COMPOUND_FORMULA_TAG); - } - - private void serializeAtomicFormula(AtomicFormula atomicFormula, XmlSerializer xmlSerializer) - throws IOException { - if (atomicFormula == null) { - return; - } - xmlSerializer.startTag(NAMESPACE, ATOMIC_FORMULA_TAG); - serializeAttributeValue( - KEY_ATTRIBUTE, String.valueOf(atomicFormula.getKey()), xmlSerializer); - if (atomicFormula.getTag() == AtomicFormula.STRING_ATOMIC_FORMULA_TAG) { - serializeAttributeValue( - VALUE_ATTRIBUTE, - ((AtomicFormula.StringAtomicFormula) atomicFormula).getValue(), - xmlSerializer); - serializeAttributeValue( - IS_HASHED_VALUE_ATTRIBUTE, - String.valueOf( - ((AtomicFormula.StringAtomicFormula) atomicFormula).getIsHashedValue()), - xmlSerializer); - } else if (atomicFormula.getTag() == AtomicFormula.LONG_ATOMIC_FORMULA_TAG) { - serializeAttributeValue( - OPERATOR_ATTRIBUTE, - String.valueOf(((AtomicFormula.LongAtomicFormula) atomicFormula).getOperator()), - xmlSerializer); - serializeAttributeValue( - VALUE_ATTRIBUTE, - String.valueOf(((AtomicFormula.LongAtomicFormula) atomicFormula).getValue()), - xmlSerializer); - } else if (atomicFormula.getTag() == AtomicFormula.BOOLEAN_ATOMIC_FORMULA_TAG) { - serializeAttributeValue( - VALUE_ATTRIBUTE, - String.valueOf(((AtomicFormula.BooleanAtomicFormula) atomicFormula).getValue()), - xmlSerializer); - } else { - throw new IllegalArgumentException( - String.format("Invalid atomic formula type: %s", atomicFormula.getClass())); - } - xmlSerializer.endTag(NAMESPACE, ATOMIC_FORMULA_TAG); - } - - private void serializeAttributeValue( - String attribute, String value, XmlSerializer xmlSerializer) throws IOException { - if (value == null) { - return; - } - xmlSerializer.attribute(NAMESPACE, attribute, value); - } -} diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index cf299fe9bb2a..1dee7a809c7e 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -18,6 +18,8 @@ package com.android.server.location; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; + import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; @@ -29,6 +31,7 @@ import android.os.RemoteException; import android.util.ArraySet; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.location.ILocationProvider; import com.android.internal.location.ILocationProviderManager; import com.android.internal.location.ProviderProperties; @@ -71,7 +74,7 @@ public class LocationProviderProxy extends AbstractLocationProvider { @Override public void onSetAdditionalProviderPackages(List<String> packageNames) { int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size()); - ArraySet<String> allPackages = new ArraySet<>(maxCount); + ArraySet<String> allPackages = new ArraySet<>(maxCount + 1); for (String packageName : packageNames) { if (packageNames.size() >= maxCount) { return; @@ -86,25 +89,39 @@ public class LocationProviderProxy extends AbstractLocationProvider { } } - // add the binder package - ComponentName service = mServiceWatcher.getBoundService().component; - if (service != null) { - allPackages.add(service.getPackageName()); - } + synchronized (mLock) { + if (!mBound) { + return; + } - setPackageNames(allPackages); + // add the binder package + ComponentName service = mServiceWatcher.getBoundService().component; + if (service != null) { + allPackages.add(service.getPackageName()); + } + + setPackageNames(allPackages); + } } // executed on binder thread @Override public void onSetAllowed(boolean allowed) { - setAllowed(allowed); + synchronized (mLock) { + if (mBound) { + setAllowed(allowed); + } + } } // executed on binder thread @Override public void onSetProperties(ProviderProperties properties) { - setProperties(properties); + synchronized (mLock) { + if (mBound) { + setProperties(properties); + } + } } // executed on binder thread @@ -114,18 +131,27 @@ public class LocationProviderProxy extends AbstractLocationProvider { } }; + // also used to synchronized any state changes (setEnabled, setProperties, setState, etc) + private final Object mLock = new Object(); + private final ServiceWatcher mServiceWatcher; - @Nullable private ProviderRequest mRequest; + @GuardedBy("mLock") + private boolean mBound; + @GuardedBy("mLock") + private ProviderRequest mRequest; private LocationProviderProxy(Context context, String action, int enableOverlayResId, int nonOverlayPackageResId) { - super(context, FgThread.getExecutor()); + // safe to use direct executor since none of our callbacks call back into any code above + // this provider - they simply forward to the proxy service + super(context, DIRECT_EXECUTOR); mServiceWatcher = new ServiceWatcher(context, FgThread.getHandler(), action, this::onBind, this::onUnbind, enableOverlayResId, nonOverlayPackageResId); - mRequest = null; + mBound = false; + mRequest = ProviderRequest.EMPTY_REQUEST; } private boolean register() { @@ -135,26 +161,35 @@ public class LocationProviderProxy extends AbstractLocationProvider { private void onBind(IBinder binder) throws RemoteException { ILocationProvider provider = ILocationProvider.Stub.asInterface(binder); - ComponentName service = mServiceWatcher.getBoundService().component; - if (service != null) { - setPackageNames(Collections.singleton(service.getPackageName())); - } + synchronized (mLock) { + mBound = true; - provider.setLocationProviderManager(mManager); + provider.setLocationProviderManager(mManager); + if (!mRequest.equals(ProviderRequest.EMPTY_REQUEST)) { + provider.setRequest(mRequest, mRequest.workSource); + } - if (mRequest != null) { - provider.setRequest(mRequest, mRequest.workSource); + ComponentName service = mServiceWatcher.getBoundService().component; + if (service != null) { + setPackageNames(Collections.singleton(service.getPackageName())); + } } } private void onUnbind() { - setState(State.EMPTY_STATE); + synchronized (mLock) { + mBound = false; + setState(State.EMPTY_STATE); + } } @Override public void onSetRequest(ProviderRequest request) { - mServiceWatcher.runOnBinder(binder -> { + synchronized (mLock) { mRequest = request; + } + + mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); service.setRequest(request, request.workSource); }); @@ -179,5 +214,8 @@ public class LocationProviderProxy extends AbstractLocationProvider { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("service=" + mServiceWatcher); + synchronized (mLock) { + pw.println("bound=" + mBound); + } } } diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java index 18615f87609f..5b4f008a581b 100644 --- a/services/core/java/com/android/server/location/MockableLocationProvider.java +++ b/services/core/java/com/android/server/location/MockableLocationProvider.java @@ -233,6 +233,9 @@ public class MockableLocationProvider extends AbstractLocationProvider { AbstractLocationProvider provider; synchronized (mOwnerLock) { provider = mProvider; + pw.println("allowed=" + getState().allowed); + pw.println("properties=" + getState().properties); + pw.println("packages=" + getState().providerPackageNames); pw.println("request=" + mRequest); } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index 97614619653e..e23542edb214 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -42,6 +42,7 @@ import java.util.Objects; /** * Maintains a connection to a particular media route provider service. */ +// TODO: Need to revisit the bind/unbind/connect/disconnect logic in this class. final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements ServiceConnection { private static final String TAG = "MR2ProviderProxy"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -252,6 +253,19 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv disconnect(); } + @Override + public void onBindingDied(ComponentName name) { + if (DEBUG) { + Slog.d(TAG, this + ": Service binding died"); + } + // TODO: Investigate whether it tries to bind endlessly when the service is + // badly implemented. + if (shouldBind()) { + unbind(); + bind(); + } + } + private void onConnectionReady(Connection connection) { if (mActiveConnection == connection) { mConnectionReady = true; diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 190557122aa4..46fb24048a20 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -16,6 +16,7 @@ package com.android.server.media; +import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -92,6 +93,20 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR PlaybackState.STATE_CONNECTING, PlaybackState.STATE_PLAYING); + private static final AudioAttributes DEFAULT_ATTRIBUTES = + new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(); + + private static int getVolumeStream(@Nullable AudioAttributes attr) { + if (attr == null) { + return DEFAULT_ATTRIBUTES.getVolumeControlStream(); + } + final int stream = attr.getVolumeControlStream(); + if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) { + return DEFAULT_ATTRIBUTES.getVolumeControlStream(); + } + return stream; + } + private final MessageHandler mHandler; private final int mOwnerPid; @@ -162,7 +177,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR mHandler = new MessageHandler(handlerLooper); mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); - mAudioAttrs = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(); + mAudioAttrs = DEFAULT_ATTRIBUTES; // May throw RemoteException if the session app is killed. mSessionCb.mCb.asBinder().linkToDeath(this, 0); @@ -262,7 +277,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { // Adjust the volume with a handler not to be blocked by other system service. - int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); + int stream = getVolumeStream(mAudioAttrs); postAdjustLocalVolume(stream, direction, flags, opPackageName, pid, uid, asSystemService, useSuggested, previousFlagPlaySound); } else { @@ -302,7 +317,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR private void setVolumeTo(String packageName, String opPackageName, int pid, int uid, int value, int flags) { if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { - int stream = AudioAttributes.toLegacyStreamType(mAudioAttrs); + int stream = getVolumeStream(mAudioAttrs); final int volumeValue = value; mHandler.post(new Runnable() { @Override @@ -720,7 +735,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR volumeType = mVolumeType; attributes = mAudioAttrs; } - int stream = AudioAttributes.toLegacyStreamType(attributes); + int stream = getVolumeStream(attributes); int max = mAudioManager.getStreamMaxVolume(stream); int current = mAudioManager.getStreamVolume(stream); return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max, diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 88fc072e2481..feb4f0edcc0d 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -48,7 +48,15 @@ public interface NotificationDelegate { int notificationLocation); void onNotificationDirectReplied(String key); void onNotificationSettingsViewed(String key); + /** + * Called when the state of {@link Notification#FLAG_BUBBLE} is changed. + */ void onNotificationBubbleChanged(String key, boolean isBubble); + /** + * Called when the state of {@link Notification.BubbleMetadata#FLAG_SUPPRESS_NOTIFICATION} + * changes. + */ + void onBubbleNotificationSuppressionChanged(String key, boolean isSuppressed); /** * Grant permission to read the specified URI to the package associated with the diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index eea59ca26f34..80f6a941bfa0 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1175,6 +1175,34 @@ public class NotificationManagerService extends SystemService { } @Override + public void onBubbleNotificationSuppressionChanged(String key, boolean isSuppressed) { + synchronized (mNotificationLock) { + NotificationRecord r = mNotificationsByKey.get(key); + if (r != null) { + Notification.BubbleMetadata data = r.getNotification().getBubbleMetadata(); + if (data == null) { + // No data, do nothing + return; + } + boolean currentlySuppressed = data.isNotificationSuppressed(); + if (currentlySuppressed == isSuppressed) { + // No changes, do nothing + return; + } + int flags = data.getFlags(); + if (isSuppressed) { + flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + } else { + flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + } + data.setFlags(flags); + mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, + true /* isAppForeground */)); + } + } + } + + @Override /** * Grant permission to read the specified URI to the package specified in the * NotificationRecord associated with the given key. The callingUid represents the UID of @@ -2682,6 +2710,7 @@ public class NotificationManagerService extends SystemService { if (callback != null && !appIsForeground && !isSystemToast) { boolean block; + long id = Binder.clearCallingIdentity(); try { block = mPlatformCompat.isChangeEnabledByPackageName( CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, pkg, @@ -2691,6 +2720,8 @@ public class NotificationManagerService extends SystemService { Slog.e(TAG, "Unexpected exception while checking block background custom toasts" + " change", e); block = false; + } finally { + Binder.restoreCallingIdentity(id); } if (block) { // TODO(b/144152069): Remove informative toast diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java index 0a9f923ac817..4c85603a719c 100644 --- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java +++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java @@ -53,10 +53,6 @@ public class OverlayActorEnforcer { */ static Pair<String, ActorState> getPackageNameForActor(String actorUriString, Map<String, Map<String, String>> namedActors) { - if (namedActors.isEmpty()) { - return Pair.create(null, ActorState.NO_NAMED_ACTORS); - } - Uri actorUri = Uri.parse(actorUriString); String actorScheme = actorUri.getScheme(); @@ -65,6 +61,10 @@ public class OverlayActorEnforcer { return Pair.create(null, ActorState.INVALID_OVERLAYABLE_ACTOR_NAME); } + if (namedActors.isEmpty()) { + return Pair.create(null, ActorState.NO_NAMED_ACTORS); + } + String actorNamespace = actorUri.getAuthority(); Map<String, String> namespace = namedActors.get(actorNamespace); if (namespace == null) { diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING index 52163a09e387..75229a1adccc 100644 --- a/services/core/java/com/android/server/om/TEST_MAPPING +++ b/services/core/java/com/android/server/om/TEST_MAPPING @@ -7,6 +7,20 @@ "include-filter": "com.android.server.om." } ] + }, + { + "name": "OverlayDeviceTests" + }, + { + "name": "OverlayHostTests" + }, + { + "name": "CtsAppSecurityHostTestCases", + "options": [ + { + "include-filter": "android.appsecurity.cts.OverlayHostTest" + } + ] } ] } diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 6e7e5d884a4a..5c17bec0db47 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -20,10 +20,12 @@ import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; import android.Manifest; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.PackageParser; import android.content.pm.parsing.AndroidPackage; import android.content.pm.parsing.ComponentParseUtils; import android.content.pm.parsing.ComponentParseUtils.ParsedActivity; @@ -108,6 +110,7 @@ public class AppsFilter { private final FeatureConfig mFeatureConfig; private final OverlayReferenceMapper mOverlayReferenceMapper; + private PackageParser.SigningDetails mSystemSigningDetails; AppsFilter(FeatureConfig featureConfig, String[] forceQueryableWhitelist, boolean systemAppsQueryable, @@ -321,6 +324,17 @@ public class AppsFilter { */ public void addPackage(PackageSetting newPkgSetting, ArrayMap<String, PackageSetting> existingSettings) { + if (Objects.equals("android", newPkgSetting.name)) { + // let's set aside the framework signatures + mSystemSigningDetails = newPkgSetting.signatures.mSigningDetails; + // and since we add overlays before we add the framework, let's revisit already added + // packages for signature matches + for (PackageSetting setting : existingSettings.values()) { + if (isSystemSigned(mSystemSigningDetails, setting)) { + mForceQueryable.add(setting.appId); + } + } + } Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage"); try { final AndroidPackage newPkg = newPkgSetting.pkg; @@ -336,7 +350,9 @@ public class AppsFilter { || (newPkgSetting.isSystem() && (mSystemAppsQueryable || ArrayUtils.contains(mForceQueryableByDevicePackageNames, newPkg.getPackageName()))); - if (newIsForceQueryable) { + if (newIsForceQueryable + || (mSystemSigningDetails != null + && isSystemSigned(mSystemSigningDetails, newPkgSetting))) { mForceQueryable.add(newPkgSetting.appId); } @@ -382,6 +398,12 @@ public class AppsFilter { } } + private static boolean isSystemSigned(@NonNull PackageParser.SigningDetails sysSigningDetails, + PackageSetting pkgSetting) { + return pkgSetting.isSystem() + && pkgSetting.signatures.mSigningDetails.signaturesMatchExactly(sysSigningDetails); + } + /** * Removes a package for consideration when filtering visibility between apps. * diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java index 0dfea7fd5f96..50cd0fcbbb17 100644 --- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -50,7 +50,7 @@ public class DataLoaderManagerService extends SystemService { private final DataLoaderManagerBinderService mBinderService; private final Object mLock = new Object(); @GuardedBy("mLock") - private SparseArray<DataLoaderServiceConnection> mServiceConnections; + private SparseArray<DataLoaderServiceConnection> mServiceConnections = new SparseArray<>(); public DataLoaderManagerService(Context context) { super(context); @@ -68,9 +68,6 @@ public class DataLoaderManagerService extends SystemService { public boolean initializeDataLoader(int dataLoaderId, Bundle params, IDataLoaderStatusListener listener) { synchronized (mLock) { - if (mServiceConnections == null) { - mServiceConnections = new SparseArray<>(); - } if (mServiceConnections.get(dataLoaderId) != null) { Slog.e(TAG, "Data loader of ID=" + dataLoaderId + " already exists."); return false; @@ -156,9 +153,6 @@ public class DataLoaderManagerService extends SystemService { @Override public @Nullable IDataLoader getDataLoader(int dataLoaderId) { synchronized (mLock) { - if (mServiceConnections == null) { - return null; - } DataLoaderServiceConnection serviceConnection = mServiceConnections.get( dataLoaderId, null); if (serviceConnection == null) { @@ -174,9 +168,6 @@ public class DataLoaderManagerService extends SystemService { @Override public void destroyDataLoader(int dataLoaderId) { synchronized (mLock) { - if (mServiceConnections == null) { - return; - } DataLoaderServiceConnection serviceConnection = mServiceConnections.get( dataLoaderId, null); diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 89030ed7c075..91bd7aea067e 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -223,7 +223,11 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_SAFE_BOOT, UserManager.DISALLOW_SHARE_LOCATION, UserManager.DISALLOW_SMS, - UserManager.DISALLOW_USB_FILE_TRANSFER + UserManager.DISALLOW_USB_FILE_TRANSFER, + UserManager.DISALLOW_AIRPLANE_MODE, + UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, + UserManager.DISALLOW_OUTGOING_CALLS, + UserManager.DISALLOW_UNMUTE_MICROPHONE ); /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index da0d82047cbe..7c76656e1e6f 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3075,7 +3075,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { event.getAction(), fallbackAction.keyCode, event.getRepeatCount(), fallbackAction.metaState, event.getDeviceId(), event.getScanCode(), - flags, event.getSource(), event.getDisplayId(), null); + flags, event.getSource(), event.getDisplayId(), null /* hmac */, null); if (!interceptFallback(focusedToken, fallbackEvent, policyFlags)) { fallbackEvent.recycle(); diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index df8e30f9387a..b45522d11a5d 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -62,6 +62,8 @@ import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.policy.WindowManagerPolicy; import com.android.server.statusbar.StatusBarManagerInternal; +import java.io.PrintWriter; + /** * Sends broadcasts about important power state changes. * <p> @@ -123,6 +125,7 @@ public class Notifier { @Nullable private final StatusBarManagerInternal mStatusBarManagerInternal; private final TrustManager mTrustManager; private final Vibrator mVibrator; + private final WakeLockLog mWakeLockLog; private final NotifierHandler mHandler; private final Intent mScreenOnIntent; @@ -190,6 +193,8 @@ public class Notifier { mShowWirelessChargingAnimationConfig = context.getResources().getBoolean( com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim); + mWakeLockLog = new WakeLockLog(); + // Initialize interactive state for battery stats. try { mBatteryStats.noteInteractive(true); @@ -228,6 +233,8 @@ public class Notifier { // Ignore } } + + mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags); } public void onLongPartialWakeLockStart(String tag, int ownerUid, WorkSource workSource, @@ -338,6 +345,7 @@ public class Notifier { // Ignore } } + mWakeLockLog.onWakeLockReleased(tag, ownerUid); } private int getBatteryStatsWakeLockMonitorType(int flags) { @@ -647,6 +655,17 @@ public class Notifier { mHandler.sendMessage(msg); } + /** + * Dumps data for bugreports. + * + * @param pw The stream to print to. + */ + public void dump(PrintWriter pw) { + if (mWakeLockLog != null) { + mWakeLockLog.dump(pw); + } + } + private void updatePendingBroadcastLocked() { if (!mBroadcastInProgress && mPendingInteractiveState != INTERACTIVE_STATE_UNKNOWN diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 3f3a13368ffc..4d13658c85b7 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -3810,6 +3810,10 @@ public final class PowerManagerService extends SystemService if (wcd != null) { wcd.dump(pw); } + + if (mNotifier != null) { + mNotifier.dump(pw); + } } private void dumpProto(FileDescriptor fd) { @@ -4268,7 +4272,7 @@ public final class PowerManagerService extends SystemService /** * Represents a wake lock that has been acquired by an application. */ - private final class WakeLock implements IBinder.DeathRecipient { + /* package */ final class WakeLock implements IBinder.DeathRecipient { public final IBinder mLock; public int mFlags; public String mTag; diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java new file mode 100644 index 000000000000..d6060fa419b9 --- /dev/null +++ b/services/core/java/com/android/server/power/WakeLockLog.java @@ -0,0 +1,1355 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; +import com.android.internal.os.SomeArgs; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.ConcurrentModificationException; +import java.util.Date; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Simple Log for wake lock events. Optimized to reduce memory usage. + * + * The wake lock events are ultimately saved in-memory in a pre-allocated byte-based ring-buffer. + * + * Most of the work of this log happens in the {@link BackgroundThread}. + * + * The main log is basically just a sequence of the two wake lock events (ACQUIRE and RELEASE). + * Each entry in the log stores the following data: + * { + * event type (RELEASE | ACQUIRE), + * time (64-bit from System.currentTimeMillis()), + * wake-lock ID {ownerUID (int) + tag (String)}, + * wake-lock flags + * } + * + * In order to maximize the number of entries that fit into the log, there are various efforts made + * to compress what we store; of which two are fairly significant and contribute the most to the + * complexity of this code: + * A) Relative Time + * - Time in each log entry is stored as an 8-bit value and is relative to the time of the + * previous event. When relative time is too large for 8-bits, we add a third type of event + * called TIME_RESET, which is used to add a new 64-bit reference-time event to the log. + * In practice, TIME_RESETs seem to make up about 10% or less of the total events depending + * on the device usage. + * B) Wake-lock tag/ID as indexes + * - Wake locks are often reused many times. To avoid storing large strings in the ring buffer, + * we maintain a {@link TagDatabase} that associates each wakelock tag with an 7-bit index. + * The main log stores only these 7-bit indexes instead of whole strings. + * + * To make the code a bit more organized, there exists a class {@link EntryByteTranslator} which + * uses the tag database, and reference-times to convert between a {@link LogEntry} and the + * byte sequence that is ultimately stored in the main log, {@link TheLog}. + */ +final class WakeLockLog { + private static final String TAG = "PowerManagerService.WLLog"; + + private static final boolean DEBUG = false; + + private static final int MSG_ON_WAKE_LOCK_EVENT = 1; + + private static final int TYPE_TIME_RESET = 0x0; + private static final int TYPE_ACQUIRE = 0x1; + private static final int TYPE_RELEASE = 0x2; + private static final int MAX_LOG_ENTRY_BYTE_SIZE = 9; + private static final int LOG_SIZE = 1024 * 10; + private static final int LOG_SIZE_MIN = MAX_LOG_ENTRY_BYTE_SIZE + 1; + + private static final int TAG_DATABASE_SIZE = 128; + private static final int TAG_DATABASE_SIZE_MAX = 128; + + private static final int LEVEL_UNKNOWN = 0; + private static final int LEVEL_PARTIAL_WAKE_LOCK = 1; + private static final int LEVEL_FULL_WAKE_LOCK = 2; + private static final int LEVEL_SCREEN_DIM_WAKE_LOCK = 3; + private static final int LEVEL_SCREEN_BRIGHT_WAKE_LOCK = 4; + private static final int LEVEL_PROXIMITY_SCREEN_OFF_WAKE_LOCK = 5; + private static final int LEVEL_DOZE_WAKE_LOCK = 6; + private static final int LEVEL_DRAW_WAKE_LOCK = 7; + + private static final String[] LEVEL_TO_STRING = { + "unknown", + "partial", + "full", + "screen-dim", + "screen-bright", + "prox", + "doze", + "draw" + }; + + /** + * Flags use the same bit field as the level, so must start at the next available bit + * after the largest level. + */ + private static final int FLAG_ON_AFTER_RELEASE = 0x8; + private static final int FLAG_ACQUIRE_CAUSES_WAKEUP = 0x10; + + private static final int MASK_LOWER_6_BITS = 0x3F; + private static final int MASK_LOWER_7_BITS = 0x7F; + + private static final String[] REDUCED_TAG_PREFIXES = + {"*job*/", "*gms_scheduler*/", "IntentOp:"}; + + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); + + /** + * Lock protects WakeLockLock.dump (binder thread) from conflicting with changes to the log + * happening on the background thread. + */ + private final Object mLock = new Object(); + + private final Injector mInjector; + private final TheLog mLog; + private final TagDatabase mTagDatabase; + private final Handler mHandler; + private final SimpleDateFormat mDumpsysDateFormat; + + WakeLockLog() { + this(new Injector()); + } + + @VisibleForTesting + WakeLockLog(Injector injector) { + mInjector = injector; + mHandler = new WakeLockLogHandler(injector.getLooper()); + mTagDatabase = new TagDatabase(injector); + EntryByteTranslator translator = new EntryByteTranslator(mTagDatabase); + mLog = new TheLog(injector, translator, mTagDatabase); + mDumpsysDateFormat = injector.getDateFormat(); + } + + /** + * Receives notifications of an ACQUIRE wake lock event from PowerManager. + * + * @param tag The wake lock tag + * @param ownerUid The owner UID of the wake lock. + * @param flags Flags used for the wake lock. + */ + public void onWakeLockAcquired(String tag, int ownerUid, int flags) { + onWakeLockEvent(TYPE_ACQUIRE, tag, ownerUid, flags); + } + + /** + * Receives notifications of a RELEASE wake lock event from PowerManager. + * + * @param tag The wake lock tag + * @param ownerUid The owner UID of the wake lock. + */ + public void onWakeLockReleased(String tag, int ownerUid) { + onWakeLockEvent(TYPE_RELEASE, tag, ownerUid, 0 /* flags */); + } + + /** + * Dumps all the wake lock data currently saved in the wake lock log to the specified + * {@code PrintWriter}. + * + * @param The {@code PrintWriter} to write to. + */ + public void dump(PrintWriter pw) { + dump(pw, false); + } + + @VisibleForTesting + void dump(PrintWriter pw, boolean includeTagDb) { + try { + synchronized (mLock) { + pw.println("Wake Lock Log"); + LogEntry tempEntry = new LogEntry(); // Temporary entry for the iterator to reuse. + final Iterator<LogEntry> iterator = mLog.getAllItems(tempEntry); + int numEvents = 0; + int numResets = 0; + while (iterator.hasNext()) { + String address = null; + if (DEBUG) { + // Gets the byte index in the log for the current entry. + address = iterator.toString(); + } + LogEntry entry = iterator.next(); + if (entry != null) { + if (entry.type == TYPE_TIME_RESET) { + numResets++; + } else { + numEvents++; + if (DEBUG) { + pw.print(address); + } + entry.dump(pw, mDumpsysDateFormat); + } + } + } + pw.println(" -"); + pw.println(" Events: " + numEvents + ", Time-Resets: " + numResets); + pw.println(" Buffer, Bytes used: " + mLog.getUsedBufferSize()); + if (DEBUG || includeTagDb) { + pw.println(" " + mTagDatabase); + } + } + } catch (Exception e) { + pw.println("Exception dumping wake-lock log: " + e.toString()); + } + } + + /** + * Adds a new entry to the log based on the specified wake lock parameters. + * + * Grabs the current time for the event and then posts the rest of the logic (actually + * adding it to the log) to a background thread. + * + * @param eventType The type of event (ACQUIRE, RELEASE); + * @param tag The wake lock's identifying tag. + * @param ownerUid The owner UID of the wake lock. + * @param flags The flags used with the wake lock. + */ + private void onWakeLockEvent(int eventType, String tag, int ownerUid, + int flags) { + if (tag == null) { + Slog.w(TAG, "Insufficient data to log wakelock [tag: " + tag + + ", ownerUid: " + ownerUid + + ", flags: 0x" + Integer.toHexString(flags)); + return; + } + + final long time = mInjector.currentTimeMillis(); + + SomeArgs args = SomeArgs.obtain(); + args.arg1 = tagNameReducer(tag); + args.argi1 = eventType; + args.argi2 = ownerUid; + args.argi3 = eventType == TYPE_ACQUIRE ? translateFlagsFromPowerManager(flags) : 0; + args.argi4 = (int) ((time >> 32) & 0xFFFFFFFFL); + args.argi5 = (int) (time & 0xFFFFFFFFL); + mHandler.obtainMessage(MSG_ON_WAKE_LOCK_EVENT, args).sendToTarget(); + } + + /** + * Handles a new wakelock event in the background thread. + * + * @param eventType The type of event (ACQUIRE, RELEASE) + * @param tag The wake lock's identifying tag. + * @param ownerUid The owner UID of the wake lock. + * @param flags the flags used with the wake lock. + */ + private void handleWakeLockEventInternal(int eventType, String tag, int ownerUid, int flags, + long time) { + synchronized (mLock) { + final TagData tagData = mTagDatabase.findOrCreateTag( + tag, ownerUid, true /* shouldCreate */); + mLog.addEntry(new LogEntry(time, eventType, tagData, flags)); + } + } + + /** + * Translates wake lock flags from PowerManager into a redefined set that fits + * in the lower 6-bits of the return value. The results are an OR-ed combination of the + * flags, {@code WakeLockLog.FLAG_*}, and a log-level, {@code WakeLockLog.LEVEL_*}. + * + * @param flags Wake lock flags including {@code PowerManager.*_WAKE_LOCK} + * {@link PowerManager.ACQUIRE_CAUSES_WAKEUP}, and + * {@link PowerManager.ON_AFTER_RELEASE}. + * @return The compressed flags value. + */ + int translateFlagsFromPowerManager(int flags) { + int newFlags = 0; + switch(PowerManager.WAKE_LOCK_LEVEL_MASK & flags) { + case PowerManager.PARTIAL_WAKE_LOCK: + newFlags = LEVEL_PARTIAL_WAKE_LOCK; + break; + case PowerManager.SCREEN_DIM_WAKE_LOCK: + newFlags = LEVEL_SCREEN_DIM_WAKE_LOCK; + break; + case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: + newFlags = LEVEL_SCREEN_BRIGHT_WAKE_LOCK; + break; + case PowerManager.FULL_WAKE_LOCK: + newFlags = LEVEL_FULL_WAKE_LOCK; + break; + case PowerManager.DOZE_WAKE_LOCK: + newFlags = LEVEL_DOZE_WAKE_LOCK; + break; + case PowerManager.DRAW_WAKE_LOCK: + newFlags = LEVEL_DRAW_WAKE_LOCK; + break; + case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: + newFlags = LEVEL_PROXIMITY_SCREEN_OFF_WAKE_LOCK; + break; + default: + Slog.w(TAG, "Unsupported lock level for logging, flags: " + flags); + break; + } + if ((flags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) { + newFlags |= FLAG_ACQUIRE_CAUSES_WAKEUP; + } + if ((flags & PowerManager.ON_AFTER_RELEASE) != 0) { + newFlags |= FLAG_ON_AFTER_RELEASE; + } + return newFlags; + } + + /** + * Reduce certain wakelock tags to something smaller. + * e.g. "*job* /com.aye.bee.cee/dee.ee.eff.Gee$Eich" -> "*job* /c.a.b.c/d.e.e.Gee$Eich" + * This is used to save space when storing the tags in the Tag Database. + * + * @param tag The tag name to reduce + * @return A reduced version of the tag name. + */ + private String tagNameReducer(String tag) { + if (tag == null) { + return null; + } + + String reduciblePrefix = null; + for (int tp = 0; tp < REDUCED_TAG_PREFIXES.length; tp++) { + if (tag.startsWith(REDUCED_TAG_PREFIXES[tp])) { + reduciblePrefix = REDUCED_TAG_PREFIXES[tp]; + break; + } + } + + if (reduciblePrefix != null) { + final StringBuilder sb = new StringBuilder(); + + // add prefix first + sb.append(tag.substring(0, reduciblePrefix.length())); + + // Stop looping on final marker + final int end = Math.max(tag.lastIndexOf("/"), tag.lastIndexOf(".")); + boolean printNext = true; + int index = sb.length(); // Start looping after the prefix + for (; index < end; index++) { + char c = tag.charAt(index); + boolean isMarker = (c == '.' || c == '/'); + // We print all markers and the character that follows each marker + if (isMarker || printNext) { + sb.append(c); + } + printNext = isMarker; + } + sb.append(tag.substring(index)); // append everything that is left + return sb.toString(); + } + return tag; + } + + /** + * Represents a wakelock-event entry in the log. + * Holds all the data of a wakelock. The lifetime of this class is fairly short as the data + * within this type is eventually written to the log as bytes and the instances discarded until + * the log is read again which is fairly infrequent. + * + * At the time of this writing, this can be one of three types of entries: + * 1) Wake lock acquire + * 2) Wake lock release + * 3) Time reset + */ + static class LogEntry { + /** + * Type of wake lock from the {@code WakeLockLog.TYPE_*} set. + */ + public int type; + + /** + * Time of the wake lock entry as taken from System.currentTimeMillis(). + */ + public long time; + + /** + * Data about the wake lock tag. + */ + public TagData tag; + + /** + * Flags used with the wake lock. + */ + public int flags; + + LogEntry() {} + + LogEntry(long time, int type, TagData tag, int flags) { + set(time, type, tag, flags); + } + + /** + * Sets the values of the log entry. + * This is exposed to ease the reuse of {@code LogEntry} instances. + * + * @param time Time of the entry. + * @param type Type of entry. + * @param tag Tag data of the wake lock. + * @param flags Flags used with the wake lock. + */ + public void set(long time, int type, TagData tag, int flags) { + this.time = time; + this.type = type; + this.tag = tag; + this.flags = flags; + } + + /** + * Dumps this entry to the specified {@link PrintWriter}. + * + * @param pw The print-writer to dump to. + * @param dateFormat The date format to use for outputing times. + */ + public void dump(PrintWriter pw, SimpleDateFormat dateFormat) { + pw.println(" " + toStringInternal(dateFormat)); + } + + /** + * Converts the entry to a string. + * date - ownerUid - (ACQ|REL) tag [(flags)] + * e.g., 1999-01-01 12:01:01.123 - 10012 - ACQ bluetooth_timer (partial) + */ + @Override + public String toString() { + return toStringInternal(DATE_FORMAT); + } + + /** + * Converts the entry to a string. + * date - ownerUid - (ACQ|REL) tag [(flags)] + * e.g., 1999-01-01 12:01:01.123 - 10012 - ACQ bluetooth_timer (partial) + * + * @param dateFormat The date format to use for outputing times. + * @return The string output of this class instance. + */ + private String toStringInternal(SimpleDateFormat dateFormat) { + StringBuilder sb = new StringBuilder(); + if (type == TYPE_TIME_RESET) { + return dateFormat.format(new Date(time)) + " - RESET"; + } + sb.append(dateFormat.format(new Date(time))) + .append(" - ") + .append(tag == null ? "---" : tag.ownerUid) + .append(" - ") + .append(type == TYPE_ACQUIRE ? "ACQ" : "REL") + .append(" ") + .append(tag == null ? "UNKNOWN" : tag.tag); + if (type == TYPE_ACQUIRE) { + sb.append(" ("); + flagsToString(sb); + sb.append(")"); + } + return sb.toString(); + } + + private void flagsToString(StringBuilder sb) { + sb.append(LEVEL_TO_STRING[flags & 0x7]); + if ((flags & FLAG_ON_AFTER_RELEASE) == FLAG_ON_AFTER_RELEASE) { + sb.append(",on-after-release"); + } + if ((flags & FLAG_ACQUIRE_CAUSES_WAKEUP) == FLAG_ACQUIRE_CAUSES_WAKEUP) { + sb.append(",acq-causes-wake"); + } + } + } + + /** + * Converts between a {@link LogEntry} instance and a byte sequence. + * + * This is used to convert {@link LogEntry}s to a series of bytes before being written into + * the log, and vice-versa when reading from the log. + * + * This method employs the compression techniques that are mentioned in the header of + * {@link WakeLockLog}: Relative-time and Tag-indexing. Please see the header for the + * description of both. + * + * The specific byte formats used are explained more thoroughly in the method {@link #toBytes}. + */ + static class EntryByteTranslator { + + // Error codes that can be returned when converting to bytes. + static final int ERROR_TIME_IS_NEGATIVE = -1; // Relative time is negative + static final int ERROR_TIME_TOO_LARGE = -2; // Relative time is out of valid range (0-255) + + private final TagDatabase mTagDatabase; + + EntryByteTranslator(TagDatabase tagDatabase) { + mTagDatabase = tagDatabase; + } + + /** + * Translates the specified bytes into a LogEntry instance, if possible. + * + * See {@link #toBytes} for an explanation of the byte formats. + * + * @param bytes The bytes to read. + * @param timeReference The reference time to use when reading the relative time from the + * bytes buffer. + * @param entryToReuse The entry instance to write to. If null, this method will create a + * new instance. + * @return The converted entry, or null if data is corrupt. + */ + LogEntry fromBytes(byte[] bytes, long timeReference, LogEntry entryToReuse) { + if (bytes == null || bytes.length == 0) { + return null; + } + + // Create an entry if non if passed in to use + LogEntry entry = entryToReuse != null ? entryToReuse : new LogEntry(); + + int type = (bytes[0] >> 6) & 0x3; + if ((type & 0x2) == 0x2) { + // As long as the highest order bit of the byte is set, it is a release + type = TYPE_RELEASE; + } + switch (type) { + case TYPE_ACQUIRE: { + if (bytes.length < 3) { + break; + } + + int flags = bytes[0] & MASK_LOWER_6_BITS; + int tagIndex = bytes[1] & MASK_LOWER_7_BITS; + TagData tag = mTagDatabase.getTag(tagIndex); + long time = (bytes[2] & 0xFF) + timeReference; + entry.set(time, TYPE_ACQUIRE, tag, flags); + return entry; + } + case TYPE_RELEASE: { + if (bytes.length < 2) { + break; + } + + int flags = 0; + int tagIndex = bytes[0] & MASK_LOWER_7_BITS; + TagData tag = mTagDatabase.getTag(tagIndex); + long time = (bytes[1] & 0xFF) + timeReference; + entry.set(time, TYPE_RELEASE, tag, flags); + return entry; + } + case TYPE_TIME_RESET: { + if (bytes.length < 9) { + break; + } + + long time = ((bytes[1] & 0xFFL) << 56) + | ((bytes[2] & 0xFFL) << 48) + | ((bytes[3] & 0xFFL) << 40) + | ((bytes[4] & 0xFFL) << 32) + | ((bytes[5] & 0xFFL) << 24) + | ((bytes[6] & 0xFFL) << 16) + | ((bytes[7] & 0xFFL) << 8) + | (bytes[8] & 0xFFL); + entry.set(time, TYPE_TIME_RESET, null, 0); + return entry; + } + default: + Slog.w(TAG, "Type not recognized [" + type + "]", new Exception()); + break; + } + return null; + } + + /** + * Converts and writes the specified entry into the specified byte array. + * If the byte array is null or too small, then the method writes nothing, but still returns + * the number of bytes necessary to write the entry. + * + * Byte format used for each type: + * + * TYPE_RELEASE: + * bits + * 0 1 2 3 4 5 6 7 + * bytes 0 [ 1 | 7-bit wake lock tag index ] + * 1 [ 8-bit relative time ] + * + * + * TYPE_ACQUIRE: + * bits + * 0 1 2 3 4 5 6 7 + * 0 [ 0 1 | wake lock flags ] + * bytes 1 [unused| 7-bit wake lock tag index ] + * 2 [ 8-bit relative time ] + * + * + * TYPE_TIME_RESET: + * bits + * 0 1 2 3 4 5 6 7 + * 0 [ 0 0 | unused ] + * bytes 1-9 [ 64-bit reference-time ] + * + * @param entry The entry to convert/write + * @param bytes The buffer to write to, or null to just return the necessary bytes. + * @param timeReference The reference-time used to calculate relative time of the entry. + * @return The number of bytes written to buffer, or required to write to the buffer. + */ + int toBytes(LogEntry entry, byte[] bytes, long timeReference) { + int sizeNeeded = -1; + switch (entry.type) { + case TYPE_ACQUIRE: { + sizeNeeded = 3; + if (bytes != null && bytes.length >= sizeNeeded) { + int relativeTime = getRelativeTime(timeReference, entry.time); + if (relativeTime < 0) { + // Negative relative time indicates error code + return relativeTime; + } + bytes[0] = (byte) ((TYPE_ACQUIRE << 6) + | (entry.flags & MASK_LOWER_6_BITS)); + bytes[1] = (byte) mTagDatabase.getTagIndex(entry.tag); + bytes[2] = (byte) (relativeTime & 0xFF); // Lower 8 bits of the time + if (DEBUG) { + Slog.d(TAG, "ACQ - Setting bytes: " + Arrays.toString(bytes)); + } + } + break; + } + case TYPE_RELEASE: { + sizeNeeded = 2; + if (bytes != null && bytes.length >= sizeNeeded) { + int relativeTime = getRelativeTime(timeReference, entry.time); + if (relativeTime < 0) { + // Negative relative time indicates error code + return relativeTime; + } + bytes[0] = (byte) (0x80 | mTagDatabase.getTagIndex(entry.tag)); + bytes[1] = (byte) (relativeTime & 0xFF); // Lower 8 bits of the time + if (DEBUG) { + Slog.d(TAG, "REL - Setting bytes: " + Arrays.toString(bytes)); + } + } + break; + } + case TYPE_TIME_RESET: { + sizeNeeded = 9; + long time = entry.time; + if (bytes != null && bytes.length >= sizeNeeded) { + bytes[0] = (TYPE_TIME_RESET << 6); + bytes[1] = (byte) ((time >> 56) & 0xFF); + bytes[2] = (byte) ((time >> 48) & 0xFF); + bytes[3] = (byte) ((time >> 40) & 0xFF); + bytes[4] = (byte) ((time >> 32) & 0xFF); + bytes[5] = (byte) ((time >> 24) & 0xFF); + bytes[6] = (byte) ((time >> 16) & 0xFF); + bytes[7] = (byte) ((time >> 8) & 0xFF); + bytes[8] = (byte) (time & 0xFF); + } + break; + } + default: + throw new RuntimeException("Unknown type " + entry); + } + + return sizeNeeded; + } + + /** + * Calculates the relative time between the specified time and timeReference. The relative + * time is expected to be non-negative and fit within 8-bits (values between 0-255). If the + * relative time is outside of that range an error code will be returned instead. + * + * @param time + * @param timeReference + * @return The relative time between time and timeReference, or an error code. + */ + private int getRelativeTime(long timeReference, long time) { + if (time < timeReference) { + if (DEBUG) { + Slog.w(TAG, "ERROR_TIME_IS_NEGATIVE"); + } + return ERROR_TIME_IS_NEGATIVE; + } + long relativeTime = time - timeReference; + if (relativeTime > 255) { + if (DEBUG) { + Slog.w(TAG, "ERROR_TIME_TOO_LARGE"); + } + return ERROR_TIME_TOO_LARGE; + } + return (int) relativeTime; + } + } + + /** + * Main implementation of the ring buffer used to store the log entries. This class takes + * {@link LogEntry} instances and adds them to the ring buffer, utilizing + * {@link EntryByteTranslator} to convert byte {@link LogEntry} to bytes within the buffer. + * + * This class also implements the logic around TIME_RESET events. Since the LogEntries store + * their time (8-bit) relative to the previous event, this class can add {@link TYPE_TIME_RESET} + * LogEntries as necessary to allow a LogEntry's relative time to fit within that range. + */ + static class TheLog { + private final EntryByteTranslator mTranslator; + + /** + * Temporary buffer used when converting a new entry to bytes for writing to the buffer. + * Allocating once allows us to avoid allocating a buffer with each write. + */ + private final byte[] mTempBuffer = new byte[MAX_LOG_ENTRY_BYTE_SIZE]; + + /** + * Second temporary buffer used when reading and writing bytes from the buffer. + * A second temporary buffer is necessary since additional items can be read concurrently + * from {@link mTempBuffer}. E.g., Adding an entry to a full buffer requires removing + * other entries from the buffer. + */ + private final byte[] mReadWriteTempBuffer = new byte[MAX_LOG_ENTRY_BYTE_SIZE]; + + /** + * Main log buffer. + */ + private final byte[] mBuffer; + + /** + * Start index of the ring buffer. + */ + private int mStart = 0; + + /** + * Current end index of the ring buffer. + */ + private int mEnd = 0; + + /** + * Start time of the entries in the buffer. The first item stores an 8-bit time that is + * relative to this value. + */ + private long mStartTime = 0; + + /** + * The time of the last entry in the buffer. Reading the time from the last entry to + * calculate the relative time of a new one is sufficiently hard to prefer saving the value + * here instead. + */ + private long mLatestTime = 0; + + /** + * Counter for number of changes (adds or removes) that have been done to the buffer. + */ + private long mChangeCount = 0; + + private final TagDatabase mTagDatabase; + + TheLog(Injector injector, EntryByteTranslator translator, TagDatabase tagDatabase) { + final int logSize = Math.max(injector.getLogSize(), LOG_SIZE_MIN); + mBuffer = new byte[logSize]; + + mTranslator = translator; + mTagDatabase = tagDatabase; + + // Register to be notified when an older tag is removed from the TagDatabase to make + // room for a new entry. + mTagDatabase.setCallback(new TagDatabase.Callback() { + @Override public void onIndexRemoved(int index) { + removeTagIndex(index); + } + }); + } + + /** + * Returns the amount of space being used in the ring buffer (in bytes). + * + * @return Used buffer size in bytes. + */ + int getUsedBufferSize() { + return mBuffer.length - getAvailableSpace(); + } + + /** + * Adds the specified {@link LogEntry} to the log by converting it to bytes and writing + * those bytes to the buffer. + * + * This method can have side effects of removing old values from the ring buffer and + * adding an extra TIME_RESET entry if necessary. + */ + void addEntry(LogEntry entry) { + if (isBufferEmpty()) { + // First item being added, do initialization. + mStartTime = mLatestTime = entry.time; + } + + int size = mTranslator.toBytes(entry, mTempBuffer, mLatestTime); + if (size == EntryByteTranslator.ERROR_TIME_IS_NEGATIVE) { + return; // Wholly unexpected circumstance...just break out now. + } else if (size == EntryByteTranslator.ERROR_TIME_TOO_LARGE) { + // The relative time between the last entry and this new one is too large + // to fit in our byte format...we need to create a new Time-Reset event and add + // that to the log first. + addEntry(new LogEntry(entry.time, TYPE_TIME_RESET, null, 0)); + size = mTranslator.toBytes(entry, mTempBuffer, mLatestTime); + } + + if (size > MAX_LOG_ENTRY_BYTE_SIZE || size <= 0) { + Slog.w(TAG, "Log entry size is out of expected range: " + size); + return; + } + + // In case the buffer is full or nearly full, ensure there is a proper amount of space + // for the new entry. + if (!makeSpace(size)) { + return; // Doesn't fit + } + + if (DEBUG) { + Slog.d(TAG, "Wrote New Entry @(" + mEnd + ") [" + entry + "] as " + + Arrays.toString(mTempBuffer)); + } + // Set the bytes and update our end index & timestamp. + writeBytesAt(mEnd, mTempBuffer, size); + if (DEBUG) { + Slog.d(TAG, "Read written Entry @(" + mEnd + ") [" + + readEntryAt(mEnd, mLatestTime, null)); + } + mEnd = (mEnd + size) % mBuffer.length; + mLatestTime = entry.time; + + TagDatabase.updateTagTime(entry.tag, entry.time); + mChangeCount++; + } + + /** + * Returns an {@link Iterator} of {@link LogEntry}s for all the entries in the log. + * + * If the log is modified while the entries are being read, the iterator will throw a + * {@link ConcurrentModificationExceptoin}. + * + * @param tempEntry A temporary {@link LogEntry} instance to use so that new instances + * aren't allocated with every call to {@code Iterator.next}. + */ + Iterator<LogEntry> getAllItems(final LogEntry tempEntry) { + return new Iterator<LogEntry>() { + private int mCurrent = mStart; // Current read position in the log. + private long mCurrentTimeReference = mStartTime; // Current time-reference to use. + private final long mChangeValue = mChangeCount; // Used to track if buffer changed. + + /** + * @return True if there are more elements to iterate through, false otherwise.\ + * @throws ConcurrentModificationException if the buffer contents change. + */ + @Override + public boolean hasNext() { + checkState(); + return mCurrent != mEnd; + } + + /** + * Returns the next element in the iterator. + * + * @return The next entry in the iterator + * @throws NoSuchElementException if iterator reaches the end. + * @throws ConcurrentModificationException if buffer contents change. + */ + @Override + public LogEntry next() { + checkState(); + + if (!hasNext()) { + throw new NoSuchElementException("No more entries left."); + } + + LogEntry entry = readEntryAt(mCurrent, mCurrentTimeReference, tempEntry); + int size = mTranslator.toBytes(entry, null, mStartTime); + mCurrent = (mCurrent + size) % mBuffer.length; + mCurrentTimeReference = entry.time; + + return entry; + } + + @Override public String toString() { + return "@" + mCurrent; + } + + /** + * @throws ConcurrentModificationException if the underlying buffer has changed + * since this iterator was instantiated. + */ + private void checkState() { + if (mChangeValue != mChangeCount) { + throw new ConcurrentModificationException("Buffer modified, old change: " + + mChangeValue + ", new change: " + mChangeCount); + } + } + }; + } + + /** + * Cleans up old tag index references from the entire log. + * Called when an older wakelock tag is removed from the tag database. This happens + * when the database needed additional room for newer tags. + * + * This is a fairly expensive operation. Reads all the entries from the buffer, which can + * be around 1500 for a 10Kb buffer. It will write back any entries that use the tag as + * well, but that's not many of them. Commonly-used tags dont ever make it to this part. + * + * If necessary, in the future we can keep track of the number of tag-users the same way we + * keep track of a tag's last-used-time to stop having to do this for old tags that dont + * have entries in the logs any more. Light testing has shown that for a 10Kb + * buffer, there are about 5 or fewer (of 1500) entries with "UNKNOWN" tag...which means + * this operation does happen, but not very much. + * + * @param tagIndex The index of the tag, as stored in the log + */ + private void removeTagIndex(int tagIndex) { + if (isBufferEmpty()) { + return; + } + + int readIndex = mStart; + long timeReference = mStartTime; + final LogEntry reusableEntryInstance = new LogEntry(); + while (readIndex != mEnd) { + LogEntry entry = readEntryAt(readIndex, timeReference, reusableEntryInstance); + if (DEBUG) { + Slog.d(TAG, "Searching to remove tags @ " + readIndex + ": " + entry); + } + if (entry == null) { + Slog.w(TAG, "Entry is unreadable - Unexpected @ " + readIndex); + break; // cannot continue if entries are now corrupt + } + if (entry.tag != null && entry.tag.index == tagIndex) { + // We found an entry that uses the tag being removed. Re-write the + // entry back without a tag. + entry.tag = null; // remove the tag, and write it back + writeEntryAt(readIndex, entry, timeReference); + if (DEBUG) { + Slog.d(TAG, "Remove tag index: " + tagIndex + " @ " + readIndex); + } + } + timeReference = entry.time; + int entryByteSize = mTranslator.toBytes(entry, null, 0L); + readIndex = (readIndex + entryByteSize) % mBuffer.length; + } + } + + /** + * Removes entries from the buffer until the specified amount of space is available for use. + * + * @param spaceNeeded The number of bytes needed in the buffer. + * @return True if there is space enough in the buffer, false otherwise. + */ + private boolean makeSpace(int spaceNeeded) { + // Test the size of the buffer can fit it first, so that we dont loop forever in the + // following while loop. + if (mBuffer.length < spaceNeeded + 1) { + return false; + } + + // We check spaceNeeded + 1 so that mStart + mEnd aren't equal. We use them being equal + // to mean that the buffer is empty...so avoid that. + while (getAvailableSpace() < (spaceNeeded + 1)) { + removeOldestItem(); + } + return true; + } + + /** + * Returns the available space of the ring buffer. + */ + private int getAvailableSpace() { + return mEnd > mStart ? mBuffer.length - (mEnd - mStart) : + (mEnd < mStart ? mStart - mEnd : + mBuffer.length); + } + + /** + * Removes the oldest item from the buffer if the buffer is not empty. + */ + private void removeOldestItem() { + if (isBufferEmpty()) { + // No items to remove + return; + } + + // Copy the contents of the start of the buffer to our temporary buffer. + LogEntry entry = readEntryAt(mStart, mStartTime, null); + if (DEBUG) { + Slog.d(TAG, "Removing oldest item at @ " + mStart + ", found: " + entry); + } + int size = mTranslator.toBytes(entry, null, mStartTime); + mStart = (mStart + size) % mBuffer.length; + mStartTime = entry.time; // new start time + mChangeCount++; + } + + /** + * Returns true if the buffer is currently unused (contains zero entries). + * + * @return True if empty, false otherwise. + */ + private boolean isBufferEmpty() { + return mStart == mEnd; + } + + /** + * Reads an entry from the specified index in the buffer. + * + * @param index Index into the buffer from which to read. + * @param timeReference Reference time to use when creating the {@link LogEntry}. + * @param entryToSet Temporary entry to use instead of allocating a new one. + * @return the log-entry instance that was read. + */ + private LogEntry readEntryAt(int index, long timeReference, LogEntry entryToSet) { + for (int i = 0; i < MAX_LOG_ENTRY_BYTE_SIZE; i++) { + int indexIntoMainBuffer = (index + i) % mBuffer.length; + if (indexIntoMainBuffer == mEnd) { + break; + } + mReadWriteTempBuffer[i] = mBuffer[indexIntoMainBuffer]; + } + return mTranslator.fromBytes(mReadWriteTempBuffer, timeReference, entryToSet); + } + + /** + * Write a specified {@link LogEntry} to the buffer at the specified index. + * + * @param index Index in which to write in the buffer. + * @param entry The entry to write into the buffer. + * @param timeReference The reference time to use when calculating the relative time. + */ + private void writeEntryAt(int index, LogEntry entry, long timeReference) { + int size = mTranslator.toBytes(entry, mReadWriteTempBuffer, timeReference); + if (size > 0) { + if (DEBUG) { + Slog.d(TAG, "Writing Entry (" + index + ") [" + entry + "] as " + + Arrays.toString(mReadWriteTempBuffer)); + } + writeBytesAt(index, mReadWriteTempBuffer, size); + } + } + + /** + * Write the specified bytes into the buffer at the specified index. + * Handling wrap-around calculation for the ring-buffer. + * + * @param index The index from which to start writing. + * @param buffer The buffer of bytes to be written. + * @param size The amount of bytes to write from {@code buffer} to the log. + */ + private void writeBytesAt(int index, byte[] buffer, int size) { + for (int i = 0; i < size; i++) { + int indexIntoMainBuffer = (index + i) % mBuffer.length; + mBuffer[indexIntoMainBuffer] = buffer[i]; + } + if (DEBUG) { + Slog.d(TAG, "Write Byte: " + Arrays.toString(buffer)); + } + } + } + + /** + * An in-memory database of wake lock {@link TagData}. All tags stored in the database are given + * a 7-bit index. This index is then used by {@link TheLog} when translating {@link LogEntry} + * instanced into bytes. + * + * If a new tag is added when the database is full, the oldest tag is removed. The oldest tag + * is calcualted using {@link TagData.lastUsedTime}. + */ + static class TagDatabase { + private final int mInvalidIndex; + private final TagData[] mArray; + private Callback mCallback; + + TagDatabase(Injector injector) { + int size = Math.min(injector.getTagDatabaseSize(), TAG_DATABASE_SIZE_MAX); + + // Largest possible index used as "INVALID", hence the (size - 1) sizing. + mArray = new TagData[size - 1]; + mInvalidIndex = size - 1; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Tag Database: size(").append(mArray.length).append(")"); + int entries = 0; + int byteEstimate = 0; + int tagSize = 0; + int tags = 0; + for (int i = 0; i < mArray.length; i++) { + byteEstimate += 8; // reference pointer + TagData data = mArray[i]; + if (data != null) { + entries++; + byteEstimate += data.getByteSize(); + if (data.tag != null) { + tags++; + tagSize += data.tag.length(); + } + } + } + sb.append(", entries: ").append(entries); + sb.append(", Bytes used: ").append(byteEstimate); + if (DEBUG) { + sb.append(", Avg tag size: ").append(tagSize / tags); + sb.append("\n ").append(Arrays.toString(mArray)); + } + return sb.toString(); + } + + /** + * Sets the callback. + * + * @param callback The callback to set. + */ + public void setCallback(Callback callback) { + mCallback = callback; + } + + /** + * Returns the tag corresponding to the specified index. + * + * @param index The index to search for. + */ + public TagData getTag(int index) { + if (index < 0 || index >= mArray.length || index == mInvalidIndex) { + return null; + } + return mArray[index]; + } + + /** + * Returns an existing tag for the specified wake lock tag + ownerUid. + * + * @param tag The wake lock tag. + * @param ownerUid The wake lock's ownerUid. + * @return the TagData instance. + */ + public TagData getTag(String tag, int ownerUid) { + return findOrCreateTag(tag, ownerUid, false /* shouldCreate */); + } + + /** + * Returns the index for the corresponding tag. + * + * @param tagData The tag-data to search for. + * @return the corresponding index, or mInvalidIndex of none is found. + */ + public int getTagIndex(TagData tagData) { + return tagData == null ? mInvalidIndex : tagData.index; + } + + /** + * Returns a tag instance for the specified wake lock tag and ownerUid. If the data + * does not exist in the database, it will be created if so specified by + * {@code shouldCreate}. + * + * @param tagStr The wake lock's tag. + * @param ownerUid The wake lock's owner Uid. + * @param shouldCreate True when the tag should be created if it doesn't already exist. + * @return The tag-data instance that was found or created. + */ + public TagData findOrCreateTag(String tagStr, int ownerUid, boolean shouldCreate) { + int firstAvailable = -1; + TagData oldest = null; + int oldestIndex = -1; + + // Loop through and find the tag to be used. + TagData tag = new TagData(tagStr, ownerUid); + for (int i = 0; i < mArray.length; i++) { + TagData current = mArray[i]; + if (tag.equals(current)) { + // found it + return current; + } else if (!shouldCreate) { + continue; + } else if (current != null) { + // See if this entry is the oldest entry, in case + // we need to replace it. + if (oldest == null || current.lastUsedTime < oldest.lastUsedTime) { + oldestIndex = i; + oldest = current; + } + } else if (firstAvailable == -1) { + firstAvailable = i; + } + } + + // Item not found, and we shouldn't create one. + if (!shouldCreate) { + return null; + } + + // If we need to remove an index, report to listeners that we are removing an index. + boolean useOldest = firstAvailable == -1; + if (useOldest && mCallback != null) { + if (DEBUG) { + Slog.d(TAG, "Removing tag index: " + oldestIndex + " = " + oldest); + } + mCallback.onIndexRemoved(oldestIndex); + } + setToIndex(tag, firstAvailable != -1 ? firstAvailable : oldestIndex); + return tag; + } + + /** + * Updates the last-used-time of the specified tag. + * + * @param tag The tag to update. + * @param time The new last-used-time for the tag. + */ + public static void updateTagTime(TagData tag, long time) { + if (tag != null) { + tag.lastUsedTime = time; + } + } + + /** + * Sets a specified tag to the specified index. + */ + private void setToIndex(TagData tag, int index) { + if (index < 0 || index >= mArray.length) { + return; + } + TagData current = mArray[index]; + if (current != null) { + // clean up the reference in the TagData instance first. + current.index = mInvalidIndex; + + if (DEBUG) { + Slog.d(TAG, "Replaced tag " + current.tag + " from index " + index + " with tag" + + tag); + } + } + + mArray[index] = tag; + tag.index = index; + } + + /** + * Callback on which to be notified of changes to {@link TagDatabase}. + */ + interface Callback { + + /** + * Handles removals of TagData indexes. + * + * @param index the index being removed. + */ + void onIndexRemoved(int index); + } + } + + /** + * This class represents unique wake lock tags that are stored in {@link TagDatabase}. + * Contains both the wake lock tag data (tag + ownerUid) as well as index and last-used + * time data as it relates to the tag-database. + */ + static class TagData { + public String tag; // Wake lock tag + public int ownerUid; // Wake lock owner Uid + public int index; // Index of the tag in the tag-database + public long lastUsedTime; // Last time that this entry was used + + TagData(String tag, int ownerUid) { + this.tag = tag; + this.ownerUid = ownerUid; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof TagData) { + TagData other = (TagData) o; + return TextUtils.equals(tag, other.tag) && ownerUid == other.ownerUid; + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (DEBUG) { + sb.append("(").append(index).append(")"); + } + return "[" + ownerUid + " ; " + tag + "]"; + } + + /** + * Returns an estimate of the number of bytes used by each instance of this class. + * Used for debug purposes. + * + * @return the size of this tag-data. + */ + int getByteSize() { + int bytes = 0; + bytes += 8; // tag reference-pointer; + bytes += tag == null ? 0 : tag.length() * 2; + bytes += 4; // ownerUid + bytes += 4; // index + bytes += 8; // lastUsedTime + return bytes; + } + } + + /** + * Injector used by {@link WakeLockLog} for testing purposes. + */ + public static class Injector { + public Looper getLooper() { + return BackgroundThread.get().getLooper(); + } + + public int getTagDatabaseSize() { + return TAG_DATABASE_SIZE; + } + + public int getLogSize() { + return LOG_SIZE; + } + + public long currentTimeMillis() { + return System.currentTimeMillis(); + } + + public SimpleDateFormat getDateFormat() { + return DATE_FORMAT; + } + } + + private class WakeLockLogHandler extends Handler { + WakeLockLogHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message message) { + switch(message.what) { + case MSG_ON_WAKE_LOCK_EVENT: + final SomeArgs args = (SomeArgs) message.obj; + final String tag = (String) args.arg1; + final int eventType = args.argi1; + final int ownerUid = args.argi2; + final int flags = args.argi3; + final long time = (((long) args.argi4) << 32) + (args.argi5 & 0xFFFFFFFFL); + args.recycle(); + handleWakeLockEventInternal(eventType, tag, ownerUid, flags, time); + break; + default: + break; + } + } + } +} diff --git a/services/core/java/com/android/server/protolog/ProtoLogImpl.java b/services/core/java/com/android/server/protolog/ProtoLogImpl.java index 1653b3d2ae28..c9d42c854b54 100644 --- a/services/core/java/com/android/server/protolog/ProtoLogImpl.java +++ b/services/core/java/com/android/server/protolog/ProtoLogImpl.java @@ -38,7 +38,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.server.protolog.common.IProtoLogGroup; import com.android.server.protolog.common.LogDataType; -import com.android.server.utils.TraceBuffer; +import com.android.internal.util.TraceBuffer; import com.android.server.wm.ProtoLogGroup; import java.io.File; 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 b12f83195961..f3197cff04a7 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -164,11 +164,24 @@ public class StatsPullAtomService extends SystemService { private static final String TAG = "StatsPullAtomService"; private static final boolean DEBUG = true; + /** + * Lowest available uid for apps. + * + * <p>Used to quickly discard memory snapshots of the zygote forks from native process + * measurements. + */ + private static final int MIN_APP_UID = 10_000; + private static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity"; /** * How long to wait on an individual subsystem to return its stats. */ private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000; + private static final long NS_PER_SEC = 1000000000; + private static final long MILLI_AMP_HR_TO_NANO_AMP_SECS = 1_000_000L * 3600L; + + private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000; + private static final int CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES = 8; private final Object mNetworkStatsLock = new Object(); @GuardedBy("mNetworkStatsLock") @@ -190,40 +203,222 @@ public class StatsPullAtomService extends SystemService { @GuardedBy("mProcessStatsLock") private IProcessStats mProcessStatsService; + private final Object mCpuTrackerLock = new Object(); + @GuardedBy("mCpuTrackerLock") + private ProcessCpuTracker mProcessCpuTracker; + + private final Object mDebugElapsedClockLock = new Object(); + @GuardedBy("mDebugElapsedClockLock") + private long mDebugElapsedClockPreviousValue = 0; + @GuardedBy("mDebugElapsedClockLock") + private long mDebugElapsedClockPullCount = 0; + + private final Object mDebugFailingElapsedClockLock = new Object(); + @GuardedBy("mDebugFailingElapsedClockLock") + private long mDebugFailingElapsedClockPreviousValue = 0; + @GuardedBy("mDebugFailingElapsedClockLock") + private long mDebugFailingElapsedClockPullCount = 0; + private final Context mContext; private StatsManager mStatsManager; private StorageManager mStorageManager; + private WifiManager mWifiManager; + private TelephonyManager mTelephony; + + private KernelWakelockReader mKernelWakelockReader; + private KernelWakelockStats mTmpWakelockStats; + + private StoragedUidIoStatsReader mStoragedUidIoStatsReader; + + private KernelCpuSpeedReader[] mKernelCpuSpeedReaders; + // Disables throttler on CPU time readers. + private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader; + private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader; + private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader; + private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader; + + private File mBaseDir; + + @Nullable + private KernelCpuThreadReaderDiff mKernelCpuThreadReader; + + private BatteryStatsHelper mBatteryStatsHelper = null; + private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS; + + private StatsPullAtomCallbackImpl mStatsCallbackImpl; public StatsPullAtomService(Context context) { super(context); mContext = context; } + /** + * Use of this StatsPullAtomCallbackImpl means we avoid one class per tagId, which we would + * get if we used lambdas. + * + * The pull methods are intentionally left to be package private to avoid the creation + * of synthetic methods to save unnecessary bytecode. + */ + private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback { + @Override + public int onPullAtom(int atomTag, List<StatsEvent> data) { + switch(atomTag) { + case StatsLog.WIFI_BYTES_TRANSFER: + return pullWifiBytesTransfer(atomTag, data); + case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: + return pullWifiBytesTransferBackground(atomTag, data); + case StatsLog.MOBILE_BYTES_TRANSFER: + return pullMobileBytesTransfer(atomTag, data); + case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: + return pullMobileBytesTransferBackground(atomTag, data); + case StatsLog.BLUETOOTH_BYTES_TRANSFER: + return pullBluetoothBytesTransfer(atomTag, data); + case StatsLog.KERNEL_WAKELOCK: + return pullKernelWakelock(atomTag, data); + case StatsLog.CPU_TIME_PER_FREQ: + return pullCpuTimePerFreq(atomTag, data); + case StatsLog.CPU_TIME_PER_UID: + return pullCpuTimePerUid(atomTag, data); + case StatsLog.CPU_TIME_PER_UID_FREQ: + return pullCpuTimeperUidFreq(atomTag, data); + case StatsLog.CPU_ACTIVE_TIME: + return pullCpuActiveTime(atomTag, data); + case StatsLog.CPU_CLUSTER_TIME: + return pullCpuClusterTime(atomTag, data); + case StatsLog.WIFI_ACTIVITY_INFO: + return pullWifiActivityInfo(atomTag, data); + case StatsLog.MODEM_ACTIVITY_INFO: + return pullModemActivityInfo(atomTag, data); + case StatsLog.BLUETOOTH_ACTIVITY_INFO: + return pullBluetoothActivityInfo(atomTag, data); + case StatsLog.SYSTEM_ELAPSED_REALTIME: + return pullSystemElapsedRealtime(atomTag, data); + case StatsLog.SYSTEM_UPTIME: + return pullSystemUptime(atomTag, data); + case StatsLog.PROCESS_MEMORY_STATE: + return pullProcessMemoryState(atomTag, data); + case StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK: + return pullProcessMemoryHighWaterMark(atomTag, data); + case StatsLog.PROCESS_MEMORY_SNAPSHOT: + return pullProcessMemorySnapshot(atomTag, data); + case StatsLog.SYSTEM_ION_HEAP_SIZE: + return pullSystemIonHeapSize(atomTag, data); + case StatsLog.ION_HEAP_SIZE: + return pullIonHeapSize(atomTag, data); + case StatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE: + return pullProcessSystemIonHeapSize(atomTag, data); + case StatsLog.TEMPERATURE: + return pullTemperature(atomTag, data); + case StatsLog.COOLING_DEVICE: + return pullCooldownDevice(atomTag, data); + case StatsLog.BINDER_CALLS: + return pullBinderCallsStats(atomTag, data); + case StatsLog.BINDER_CALLS_EXCEPTIONS: + return pullBinderCallsStatsExceptions(atomTag, data); + case StatsLog.LOOPER_STATS: + return pullLooperStats(atomTag, data); + case StatsLog.DISK_STATS: + return pullDiskStats(atomTag, data); + case StatsLog.DIRECTORY_USAGE: + return pullDirectoryUsage(atomTag, data); + case StatsLog.APP_SIZE: + return pullAppSize(atomTag, data); + case StatsLog.CATEGORY_SIZE: + return pullCategorySize(atomTag, data); + case StatsLog.NUM_FINGERPRINTS_ENROLLED: + return pullNumBiometricsEnrolled( + BiometricsProtoEnums.MODALITY_FINGERPRINT, atomTag, data); + case StatsLog.NUM_FACES_ENROLLED: + return pullNumBiometricsEnrolled( + BiometricsProtoEnums.MODALITY_FACE, atomTag, data); + case StatsLog.PROC_STATS: + return pullProcStats(ProcessStats.REPORT_ALL, atomTag, data); + case StatsLog.PROC_STATS_PKG_PROC: + return pullProcStats(ProcessStats.REPORT_PKG_PROC_STATS, atomTag, data); + case StatsLog.DISK_IO: + return pullDiskIO(atomTag, data); + case StatsLog.POWER_PROFILE: + return pullPowerProfile(atomTag, data); + case StatsLog.PROCESS_CPU_TIME: + return pullProcessCpuTime(atomTag, data); + case StatsLog.CPU_TIME_PER_THREAD_FREQ: + return pullCpuTimePerThreadFreq(atomTag, data); + case StatsLog.DEVICE_CALCULATED_POWER_USE: + return pullDeviceCalculatedPowerUse(atomTag, data); + case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: + return pullDeviceCalculatedPowerBlameUid(atomTag, data); + case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: + return pullDeviceCalculatedPowerBlameOther(atomTag, data); + case StatsLog.DEBUG_ELAPSED_CLOCK: + return pullDebugElapsedClock(atomTag, data); + case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: + return pullDebugFailingElapsedClock(atomTag, data); + case StatsLog.BUILD_INFORMATION: + return pullBuildInformation(atomTag, data); + case StatsLog.ROLE_HOLDER: + return pullRoleHolder(atomTag, data); + case StatsLog.DANGEROUS_PERMISSION_STATE: + return pullDangerousPermissionState(atomTag, data); + case StatsLog.TIME_ZONE_DATA_INFO: + return pullTimeZoneDataInfo(atomTag, data); + case StatsLog.EXTERNAL_STORAGE_INFO: + return pullExternalStorageInfo(atomTag, data); + case StatsLog.APPS_ON_EXTERNAL_STORAGE_INFO: + return pullAppsOnExternalStorageInfo(atomTag, data); + case StatsLog.FACE_SETTINGS: + return pullFaceSettings(atomTag, data); + case StatsLog.APP_OPS: + return pullAppOps(atomTag, data); + case StatsLog.NOTIFICATION_REMOTE_VIEWS: + return pullNotificationRemoteViews(atomTag, data); + case StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED: + return pullDangerousPermissionState(atomTag, data); + default: + throw new UnsupportedOperationException("Unknown tagId=" + atomTag); + } + } + } + @Override public void onStart() { + // no op + } + + @Override + public void onBootPhase(int phase) { + super.onBootPhase(phase); + if (phase == PHASE_SYSTEM_SERVICES_READY) { + BackgroundThread.getHandler().post(() -> { + initializePullersState(); + registerAllPullers(); + registerEventListeners(); + }); + } + } + + void initializePullersState() { + // Get Context Managers mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER); mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); mTelephony = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class); - final ConnectivityManager connectivityManager = - (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - // Default NetworkRequest should cover all transport types. - final NetworkRequest request = new NetworkRequest.Builder().build(); - connectivityManager.registerNetworkCallback(request, new ConnectivityStatsCallback()); + // Initialize DiskIO + mStoragedUidIoStatsReader = new StoragedUidIoStatsReader(); - // Enable push notifications of throttling from vendor thermal - // management subsystem via thermalservice. - IThermalService thermalService = getIThermalService(); - if (thermalService != null) { - try { - thermalService.registerThermalEventListener( - new ThermalEventListener()); - Slog.i(TAG, "register thermal listener successfully"); - } catch (RemoteException e) { - Slog.i(TAG, "failed to register thermal listener"); - } - } + // Initialize PROC_STATS + // TODO (b/148402814): Change this directory to stats_pull. + mBaseDir = new File(SystemServiceManager.ensureSystemDir(), "stats_companion"); + + // Disables throttler on CPU time readers. + mCpuUidUserSysTimeReader = new KernelCpuUidUserSysTimeReader(false); + mCpuUidFreqTimeReader = new KernelCpuUidFreqTimeReader(false); + mCpuUidActiveTimeReader = new KernelCpuUidActiveTimeReader(false); + mCpuUidClusterTimeReader = new KernelCpuUidClusterTimeReader(false); + + // Initialize state for KERNEL_WAKELOCK + mKernelWakelockReader = new KernelWakelockReader(); + mTmpWakelockStats = new KernelWakelockStats(); // Initialize state for CPU_TIME_PER_FREQ atom PowerProfile powerProfile = new PowerProfile(mContext); @@ -245,13 +440,23 @@ public class StatsPullAtomService extends SystemService { mBaseDir.mkdirs(); } - @Override - public void onBootPhase(int phase) { - super.onBootPhase(phase); - if (phase == PHASE_SYSTEM_SERVICES_READY) { - BackgroundThread.getHandler().post(() -> { - registerAllPullers(); - }); + void registerEventListeners() { + final ConnectivityManager connectivityManager = + (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + // Default NetworkRequest should cover all transport types. + final NetworkRequest request = new NetworkRequest.Builder().build(); + connectivityManager.registerNetworkCallback(request, new ConnectivityStatsCallback()); + + // Enable push notifications of throttling from vendor thermal + // management subsystem via thermalservice. + IThermalService thermalService = getIThermalService(); + if (thermalService != null) { + try { + thermalService.registerThermalEventListener(new ThermalEventListener()); + Slog.i(TAG, "register thermal listener successfully"); + } catch (RemoteException e) { + Slog.i(TAG, "failed to register thermal listener"); + } } } @@ -259,6 +464,7 @@ public class StatsPullAtomService extends SystemService { if (DEBUG) { Slog.d(TAG, "Registering all pullers with statsd"); } + mStatsCallbackImpl = new StatsPullAtomCallbackImpl(); registerWifiBytesTransfer(); registerWifiBytesTransferBackground(); registerMobileBytesTransfer(); @@ -275,11 +481,6 @@ public class StatsPullAtomService extends SystemService { registerBluetoothActivityInfo(); registerSystemElapsedRealtime(); registerSystemUptime(); - registerRemainingBatteryCapacity(); - registerFullBatteryCapacity(); - registerBatteryVoltage(); - registerBatteryLevel(); - registerBatteryCycleCount(); registerProcessMemoryState(); registerProcessMemoryHighWaterMark(); registerProcessMemorySnapshot(); @@ -440,11 +641,11 @@ public class StatsPullAtomService extends SystemService { tagId, metadata, BackgroundThread.getExecutor(), - (atomTag, data) -> pullWifiBytesTransfer(atomTag, data) + mStatsCallbackImpl ); } - private int pullWifiBytesTransfer(int atomTag, List<StatsEvent> pulledData) { + int pullWifiBytesTransfer(int atomTag, List<StatsEvent> pulledData) { INetworkStatsService networkStatsService = getINetworkStatsService(); if (networkStatsService == null) { Slog.e(TAG, "NetworkStats Service is not available!"); @@ -452,7 +653,7 @@ public class StatsPullAtomService extends SystemService { } long token = Binder.clearCallingIdentity(); try { - // TODO: Consider caching the following call to get BatteryStatsInternal. + // TODO(b/148402814): Consider caching the following call to get BatteryStatsInternal. BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); String[] ifaces = bs.getWifiIfaces(); if (ifaces.length == 0) { @@ -532,11 +733,11 @@ public class StatsPullAtomService extends SystemService { tagId, metadata, BackgroundThread.getExecutor(), - (atomTag, data) -> pullWifiBytesTransferBackground(atomTag, data) + mStatsCallbackImpl ); } - private int pullWifiBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) { + int pullWifiBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) { INetworkStatsService networkStatsService = getINetworkStatsService(); if (networkStatsService == null) { Slog.e(TAG, "NetworkStats Service is not available!"); @@ -570,11 +771,11 @@ public class StatsPullAtomService extends SystemService { tagId, metadata, BackgroundThread.getExecutor(), - (atomTag, data) -> pullMobileBytesTransfer(atomTag, data) + mStatsCallbackImpl ); } - private int pullMobileBytesTransfer(int atomTag, List<StatsEvent> pulledData) { + int pullMobileBytesTransfer(int atomTag, List<StatsEvent> pulledData) { INetworkStatsService networkStatsService = getINetworkStatsService(); if (networkStatsService == null) { Slog.e(TAG, "NetworkStats Service is not available!"); @@ -608,11 +809,11 @@ public class StatsPullAtomService extends SystemService { tagId, metadata, BackgroundThread.getExecutor(), - (atomTag, data) -> pullMobileBytesTransferBackground(atomTag, data) + mStatsCallbackImpl ); } - private int pullMobileBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) { + int pullMobileBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) { INetworkStatsService networkStatsService = getINetworkStatsService(); if (networkStatsService == null) { Slog.e(TAG, "NetworkStats Service is not available!"); @@ -646,7 +847,7 @@ public class StatsPullAtomService extends SystemService { tagId, metadata, BackgroundThread.getExecutor(), - (atomTag, data) -> pullBluetoothBytesTransfer(atomTag, data) + mStatsCallbackImpl ); } @@ -693,7 +894,7 @@ public class StatsPullAtomService extends SystemService { } } - private int pullBluetoothBytesTransfer(int atomTag, List<StatsEvent> pulledData) { + int pullBluetoothBytesTransfer(int atomTag, List<StatsEvent> pulledData) { BluetoothActivityEnergyInfo info = fetchBluetoothData(); if (info == null || info.getUidTraffic() == null) { return StatsManager.PULL_SKIP; @@ -710,20 +911,17 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); - private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); - private void registerKernelWakelock() { int tagId = StatsLog.KERNEL_WAKELOCK; mStatsManager.registerPullAtomCallback( tagId, /* PullAtomMetadata */ null, BackgroundThread.getExecutor(), - (atomTag, data) -> pullKernelWakelock(atomTag, data) + mStatsCallbackImpl ); } - private int pullKernelWakelock(int atomTag, List<StatsEvent> pulledData) { + int pullKernelWakelock(int atomTag, List<StatsEvent> pulledData) { final KernelWakelockStats wakelockStats = mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats); for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) { @@ -741,17 +939,6 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - private KernelCpuSpeedReader[] mKernelCpuSpeedReaders; - // Disables throttler on CPU time readers. - private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader = - new KernelCpuUidUserSysTimeReader(false); - private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader = - new KernelCpuUidFreqTimeReader(false); - private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader = - new KernelCpuUidActiveTimeReader(false); - private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader = - new KernelCpuUidClusterTimeReader(false); - private void registerCpuTimePerFreq() { int tagId = StatsLog.CPU_TIME_PER_FREQ; PullAtomMetadata metadata = new PullAtomMetadata.Builder() @@ -761,11 +948,11 @@ public class StatsPullAtomService extends SystemService { tagId, metadata, BackgroundThread.getExecutor(), - (atomTag, data) -> pullCpuTimePerFreq(atomTag, data) + mStatsCallbackImpl ); } - private int pullCpuTimePerFreq(int atomTag, List<StatsEvent> pulledData) { + int pullCpuTimePerFreq(int atomTag, List<StatsEvent> pulledData) { for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) { long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute(); if (clusterTimeMs != null) { @@ -792,11 +979,11 @@ public class StatsPullAtomService extends SystemService { tagId, metadata, BackgroundThread.getExecutor(), - (atomTag, data) -> pullCpuTimePerUid(atomTag, data) + mStatsCallbackImpl ); } - private int pullCpuTimePerUid(int atomTag, List<StatsEvent> pulledData) { + int pullCpuTimePerUid(int atomTag, List<StatsEvent> pulledData) { mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> { long userTimeUs = timesUs[0], systemTimeUs = timesUs[1]; StatsEvent e = StatsEvent.newBuilder() @@ -821,11 +1008,11 @@ public class StatsPullAtomService extends SystemService { tagId, metadata, BackgroundThread.getExecutor(), - (atomTag, data) -> pullCpuTimeperUidFreq(atomTag, data) + mStatsCallbackImpl ); } - private int pullCpuTimeperUidFreq(int atomTag, List<StatsEvent> pulledData) { + int pullCpuTimeperUidFreq(int atomTag, List<StatsEvent> pulledData) { mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> { for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) { if (cpuFreqTimeMs[freqIndex] != 0) { @@ -853,11 +1040,11 @@ public class StatsPullAtomService extends SystemService { tagId, metadata, BackgroundThread.getExecutor(), - (atomTag, data) -> pullCpuActiveTime(atomTag, data) + mStatsCallbackImpl ); } - private int pullCpuActiveTime(int atomTag, List<StatsEvent> pulledData) { + int pullCpuActiveTime(int atomTag, List<StatsEvent> pulledData) { mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) @@ -880,11 +1067,11 @@ public class StatsPullAtomService extends SystemService { tagId, metadata, BackgroundThread.getExecutor(), - (atomTag, data) -> pullCpuClusterTime(atomTag, data) + mStatsCallbackImpl ); } - private int pullCpuClusterTime(int atomTag, List<StatsEvent> pulledData) { + int pullCpuClusterTime(int atomTag, List<StatsEvent> pulledData) { mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> { for (int i = 0; i < cpuClusterTimesMs.length; i++) { StatsEvent e = StatsEvent.newBuilder() @@ -904,15 +1091,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullWifiActivityInfo(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private WifiManager mWifiManager; - private TelephonyManager mTelephony; - - private int pullWifiActivityInfo(int atomTag, List<StatsEvent> pulledData) { + int pullWifiActivityInfo(int atomTag, List<StatsEvent> pulledData) { long token = Binder.clearCallingIdentity(); try { SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi"); @@ -959,12 +1143,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullModemActivityInfo(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullModemActivityInfo(int atomTag, List<StatsEvent> pulledData) { + int pullModemActivityInfo(int atomTag, List<StatsEvent> pulledData) { long token = Binder.clearCallingIdentity(); try { SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony"); @@ -998,11 +1182,11 @@ public class StatsPullAtomService extends SystemService { tagId, /* metadata */ null, BackgroundThread.getExecutor(), - (atomTag, data) -> pullBluetoothActivityInfo(atomTag, data) + mStatsCallbackImpl ); } - private int pullBluetoothActivityInfo(int atomTag, List<StatsEvent> pulledData) { + int pullBluetoothActivityInfo(int atomTag, List<StatsEvent> pulledData) { BluetoothActivityEnergyInfo info = fetchBluetoothData(); if (info == null) { return StatsManager.PULL_SKIP; @@ -1020,23 +1204,21 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - private static final long NS_PER_SEC = 1000000000; - private void registerSystemElapsedRealtime() { int tagId = StatsLog.SYSTEM_ELAPSED_REALTIME; - PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setCoolDownNs(NS_PER_SEC) .setTimeoutNs(NS_PER_SEC / 2) .build(); mStatsManager.registerPullAtomCallback( tagId, metadata, - (atomTag, data) -> pullSystemElapsedRealtime(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullSystemElapsedRealtime(int atomTag, List<StatsEvent> pulledData) { + int pullSystemElapsedRealtime(int atomTag, List<StatsEvent> pulledData) { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeLong(SystemClock.elapsedRealtime()) @@ -1051,11 +1233,11 @@ public class StatsPullAtomService extends SystemService { tagId, null, // use default PullAtomMetadata values BackgroundThread.getExecutor(), - (atomTag, data) -> pullSystemUptime(atomTag, data) + mStatsCallbackImpl ); } - private int pullSystemUptime(int atomTag, List<StatsEvent> pulledData) { + int pullSystemUptime(int atomTag, List<StatsEvent> pulledData) { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeLong(SystemClock.elapsedRealtime()) @@ -1064,60 +1246,20 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - private void registerRemainingBatteryCapacity() { - // No op. - } - - private void pullRemainingBatteryCapacity() { - // No op. - } - - private void registerFullBatteryCapacity() { - // No op. - } - - private void pullFullBatteryCapacity() { - // No op. - } - - private void registerBatteryVoltage() { - // No op. - } - - private void pullBatteryVoltage() { - // No op. - } - - private void registerBatteryLevel() { - // No op. - } - - private void pullBatteryLevel() { - // No op. - } - - private void registerBatteryCycleCount() { - // No op. - } - - private void pullBatteryCycleCount() { - // No op. - } - private void registerProcessMemoryState() { int tagId = StatsLog.PROCESS_MEMORY_STATE; - PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setAdditiveFields(new int[] {4, 5, 6, 7, 8}) .build(); mStatsManager.registerPullAtomCallback( tagId, metadata, - (atomTag, data) -> pullProcessMemoryState(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullProcessMemoryState(int atomTag, List<StatsEvent> pulledData) { + int pullProcessMemoryState(int atomTag, List<StatsEvent> pulledData) { List<ProcessMemoryState> processMemoryStates = LocalServices.getService(ActivityManagerInternal.class) .getMemoryStateForProcesses(); @@ -1146,14 +1288,6 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - /** - * Lowest available uid for apps. - * - * <p>Used to quickly discard memory snapshots of the zygote forks from native process - * measurements. - */ - private static final int MIN_APP_UID = 10_000; - private static boolean isAppUid(int uid) { return uid >= MIN_APP_UID; } @@ -1163,12 +1297,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullProcessMemoryHighWaterMark(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullProcessMemoryHighWaterMark(int atomTag, List<StatsEvent> pulledData) { + int pullProcessMemoryHighWaterMark(int atomTag, List<StatsEvent> pulledData) { List<ProcessMemoryState> managedProcessList = LocalServices.getService(ActivityManagerInternal.class) .getMemoryStateForProcesses(); @@ -1216,12 +1350,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullProcessMemorySnapshot(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullProcessMemorySnapshot(int atomTag, List<StatsEvent> pulledData) { + int pullProcessMemorySnapshot(int atomTag, List<StatsEvent> pulledData) { List<ProcessMemoryState> managedProcessList = LocalServices.getService(ActivityManagerInternal.class) .getMemoryStateForProcesses(); @@ -1276,12 +1410,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullSystemIonHeapSize(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullSystemIonHeapSize(int atomTag, List<StatsEvent> pulledData) { + int pullSystemIonHeapSize(int atomTag, List<StatsEvent> pulledData) { final long systemIonHeapSizeInBytes = readSystemIonHeapSizeFromDebugfs(); StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) @@ -1297,11 +1431,11 @@ public class StatsPullAtomService extends SystemService { tagId, /* PullAtomMetadata */ null, BackgroundThread.getExecutor(), - (atomTag, data) -> pullIonHeapSize(atomTag, data) + mStatsCallbackImpl ); } - private int pullIonHeapSize(int atomTag, List<StatsEvent> pulledData) { + int pullIonHeapSize(int atomTag, List<StatsEvent> pulledData) { int ionHeapSizeInKilobytes = (int) getIonHeapsSizeKb(); StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) @@ -1316,12 +1450,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullProcessSystemIonHeapSize(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullProcessSystemIonHeapSize(int atomTag, List<StatsEvent> pulledData) { + int pullProcessSystemIonHeapSize(int atomTag, List<StatsEvent> pulledData) { List<IonAllocations> result = readProcessSystemIonHeapSizesFromDebugfs(); for (IonAllocations allocations : result) { StatsEvent e = StatsEvent.newBuilder() @@ -1342,12 +1476,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullTemperature(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullTemperature(int atomTag, List<StatsEvent> pulledData) { + int pullTemperature(int atomTag, List<StatsEvent> pulledData) { IThermalService thermalService = getIThermalService(); if (thermalService == null) { return StatsManager.PULL_SKIP; @@ -1380,12 +1514,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullCooldownDevice(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullCooldownDevice(int atomTag, List<StatsEvent> pulledData) { + int pullCooldownDevice(int atomTag, List<StatsEvent> pulledData) { IThermalService thermalService = getIThermalService(); if (thermalService == null) { return StatsManager.PULL_SKIP; @@ -1414,18 +1548,18 @@ public class StatsPullAtomService extends SystemService { private void registerBinderCallsStats() { int tagId = StatsLog.BINDER_CALLS; - PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setAdditiveFields(new int[] {4, 5, 6, 8, 12}) .build(); mStatsManager.registerPullAtomCallback( tagId, metadata, - (atomTag, data) -> pullBinderCallsStats(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullBinderCallsStats(int atomTag, List<StatsEvent> pulledData) { + int pullBinderCallsStats(int atomTag, List<StatsEvent> pulledData) { BinderCallsStatsService.Internal binderStats = LocalServices.getService(BinderCallsStatsService.Internal.class); if (binderStats == null) { @@ -1463,12 +1597,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullBinderCallsStatsExceptions(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullBinderCallsStatsExceptions(int atomTag, List<StatsEvent> pulledData) { + int pullBinderCallsStatsExceptions(int atomTag, List<StatsEvent> pulledData) { BinderCallsStatsService.Internal binderStats = LocalServices.getService(BinderCallsStatsService.Internal.class); if (binderStats == null) { @@ -1492,18 +1626,18 @@ public class StatsPullAtomService extends SystemService { private void registerLooperStats() { int tagId = StatsLog.LOOPER_STATS; - PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setAdditiveFields(new int[] {5, 6, 7, 8, 9}) .build(); mStatsManager.registerPullAtomCallback( tagId, metadata, - (atomTag, data) -> pullLooperStats(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullLooperStats(int atomTag, List<StatsEvent> pulledData) { + int pullLooperStats(int atomTag, List<StatsEvent> pulledData) { LooperStats looperStats = LocalServices.getService(LooperStats.class); if (looperStats == null) { return StatsManager.PULL_SKIP; @@ -1540,12 +1674,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullDiskStats(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullDiskStats(int atomTag, List<StatsEvent> pulledData) { + int pullDiskStats(int atomTag, List<StatsEvent> pulledData) { // Run a quick-and-dirty performance test: write 512 bytes byte[] junk = new byte[512]; for (int i = 0; i < junk.length; i++) junk[i] = (byte) i; // Write nonzero bytes @@ -1606,12 +1740,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullDirectoryUsage(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullDirectoryUsage(int atomTag, List<StatsEvent> pulledData) { + int pullDirectoryUsage(int atomTag, List<StatsEvent> pulledData) { StatFs statFsData = new StatFs(Environment.getDataDirectory().getAbsolutePath()); StatFs statFsSystem = new StatFs(Environment.getRootDirectory().getAbsolutePath()); StatFs statFsCache = new StatFs(Environment.getDownloadCacheDirectory().getAbsolutePath()); @@ -1647,12 +1781,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullAppSize(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullAppSize(int atomTag, List<StatsEvent> pulledData) { + int pullAppSize(int atomTag, List<StatsEvent> pulledData) { try { String jsonStr = IoUtils.readFileAsString(DiskStatsLoggingService.DUMPSYS_CACHE_PATH); JSONObject json = new JSONObject(jsonStr); @@ -1691,12 +1825,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullCategorySize(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullCategorySize(int atomTag, List<StatsEvent> pulledData) { + int pullCategorySize(int atomTag, List<StatsEvent> pulledData) { try { String jsonStr = IoUtils.readFileAsString(DiskStatsLoggingService.DUMPSYS_CACHE_PATH); JSONObject json = new JSONObject(jsonStr); @@ -1794,9 +1928,8 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullNumBiometricsEnrolled( - BiometricsProtoEnums.MODALITY_FINGERPRINT, atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } @@ -1805,9 +1938,8 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullNumBiometricsEnrolled( - BiometricsProtoEnums.MODALITY_FACE, atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } @@ -1859,15 +1991,13 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - private File mBaseDir = new File(SystemServiceManager.ensureSystemDir(), "stats_companion"); - private void registerProcStats() { int tagId = StatsLog.PROC_STATS; mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullProcStats(ProcessStats.REPORT_ALL, atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } @@ -1876,8 +2006,8 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullProcStats(ProcessStats.REPORT_PKG_PROC_STATS, atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } @@ -1940,25 +2070,21 @@ public class StatsPullAtomService extends SystemService { return 0; } - - private StoragedUidIoStatsReader mStoragedUidIoStatsReader = - new StoragedUidIoStatsReader(); - private void registerDiskIO() { int tagId = StatsLog.DISK_IO; - PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setAdditiveFields(new int[] {2, 3, 4, 5, 6, 7, 8, 9, 10, 11}) .setCoolDownNs(3 * NS_PER_SEC) .build(); mStatsManager.registerPullAtomCallback( tagId, metadata, - (atomTag, data) -> pullDiskIO(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullDiskIO(int atomTag, List<StatsEvent> pulledData) { + int pullDiskIO(int atomTag, List<StatsEvent> pulledData) { mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead, fgBytesWrite, bgCharsRead, bgCharsWrite, bgBytesRead, bgBytesWrite, fgFsync, bgFsync) -> { @@ -1987,11 +2113,11 @@ public class StatsPullAtomService extends SystemService { tagId, /* PullAtomMetadata */ null, BackgroundThread.getExecutor(), - (atomTag, data) -> pullPowerProfile(atomTag, data) + mStatsCallbackImpl ); } - private int pullPowerProfile(int atomTag, List<StatsEvent> pulledData) { + int pullPowerProfile(int atomTag, List<StatsEvent> pulledData) { PowerProfile powerProfile = new PowerProfile(mContext); ProtoOutputStream proto = new ProtoOutputStream(); powerProfile.dumpDebug(proto); @@ -2004,25 +2130,21 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - private final Object mCpuTrackerLock = new Object(); - @GuardedBy("mCpuTrackerLock") - private ProcessCpuTracker mProcessCpuTracker; - private void registerProcessCpuTime() { int tagId = StatsLog.PROCESS_CPU_TIME; // Min cool-down is 5 sec, in line with what ActivityManagerService uses. - PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setCoolDownNs(5 * NS_PER_SEC) .build(); mStatsManager.registerPullAtomCallback( tagId, metadata, - (atomTag, data) -> pullProcessCpuTime(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullProcessCpuTime(int atomTag, List<StatsEvent> pulledData) { + int pullProcessCpuTime(int atomTag, List<StatsEvent> pulledData) { synchronized (mCpuTrackerLock) { if (mProcessCpuTracker == null) { mProcessCpuTracker = new ProcessCpuTracker(false); @@ -2044,24 +2166,20 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - @Nullable - private KernelCpuThreadReaderDiff mKernelCpuThreadReader; - private static final int CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES = 8; - private void registerCpuTimePerThreadFreq() { int tagId = StatsLog.CPU_TIME_PER_THREAD_FREQ; - PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setAdditiveFields(new int[] {7, 9, 11, 13, 15, 17, 19, 21}) .build(); mStatsManager.registerPullAtomCallback( tagId, metadata, - (atomTag, data) -> pullCpuTimePerThreadFreq(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullCpuTimePerThreadFreq(int atomTag, List<StatsEvent> pulledData) { + int pullCpuTimePerThreadFreq(int atomTag, List<StatsEvent> pulledData) { if (this.mKernelCpuThreadReader == null) { Slog.e(TAG, "mKernelCpuThreadReader is null"); return StatsManager.PULL_SKIP; @@ -2118,12 +2236,6 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - // TODO: move to top of file when all migrations are complete - private BatteryStatsHelper mBatteryStatsHelper = null; - private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000; - private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS; - private static final long MILLI_AMP_HR_TO_NANO_AMP_SECS = 1_000_000L * 3600L; - private BatteryStatsHelper getBatteryStatsHelper() { if (mBatteryStatsHelper == null) { final long callingToken = Binder.clearCallingIdentity(); @@ -2155,12 +2267,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullDeviceCalculatedPowerUse(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullDeviceCalculatedPowerUse(int atomTag, List<StatsEvent> pulledData) { + int pullDeviceCalculatedPowerUse(int atomTag, List<StatsEvent> pulledData) { BatteryStatsHelper bsHelper = getBatteryStatsHelper(); StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) @@ -2175,12 +2287,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullDeviceCalculatedPowerBlameUid(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullDeviceCalculatedPowerBlameUid(int atomTag, List<StatsEvent> pulledData) { + int pullDeviceCalculatedPowerBlameUid(int atomTag, List<StatsEvent> pulledData) { final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList(); if (sippers == null) { return StatsManager.PULL_SKIP; @@ -2205,12 +2317,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullDeviceCalculatedPowerBlameOther(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullDeviceCalculatedPowerBlameOther(int atomTag, List<StatsEvent> pulledData) { + int pullDeviceCalculatedPowerBlameOther(int atomTag, List<StatsEvent> pulledData) { final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList(); if (sippers == null) { return StatsManager.PULL_SKIP; @@ -2233,24 +2345,20 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - private final Object mDebugElapsedClockLock = new Object(); - private long mDebugElapsedClockPreviousValue = 0; - private long mDebugElapsedClockPullCount = 0; - private void registerDebugElapsedClock() { int tagId = StatsLog.DEBUG_ELAPSED_CLOCK; - PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setAdditiveFields(new int[] {1, 2, 3, 4}) .build(); mStatsManager.registerPullAtomCallback( tagId, metadata, - (atomTag, data) -> pullDebugElapsedClock(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullDebugElapsedClock(int atomTag, List<StatsEvent> pulledData) { + int pullDebugElapsedClock(int atomTag, List<StatsEvent> pulledData) { final long elapsedMillis = SystemClock.elapsedRealtime(); synchronized (mDebugElapsedClockLock) { @@ -2288,24 +2396,20 @@ public class StatsPullAtomService extends SystemService { return StatsManager.PULL_SUCCESS; } - private final Object mDebugFailingElapsedClockLock = new Object(); - private long mDebugFailingElapsedClockPreviousValue = 0; - private long mDebugFailingElapsedClockPullCount = 0; - private void registerDebugFailingElapsedClock() { int tagId = StatsLog.DEBUG_FAILING_ELAPSED_CLOCK; - PullAtomMetadata metadata = PullAtomMetadata.newBuilder() + PullAtomMetadata metadata = new PullAtomMetadata.Builder() .setAdditiveFields(new int[] {1, 2, 3, 4}) .build(); mStatsManager.registerPullAtomCallback( tagId, metadata, - (atomTag, data) -> pullDebugFailingElapsedClock(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullDebugFailingElapsedClock(int atomTag, List<StatsEvent> pulledData) { + int pullDebugFailingElapsedClock(int atomTag, List<StatsEvent> pulledData) { final long elapsedMillis = SystemClock.elapsedRealtime(); synchronized (mDebugFailingElapsedClockLock) { @@ -2339,11 +2443,11 @@ public class StatsPullAtomService extends SystemService { tagId, null, // use default PullAtomMetadata values BackgroundThread.getExecutor(), - (atomTag, data) -> pullBuildInformation(atomTag, data) + mStatsCallbackImpl ); } - private int pullBuildInformation(int atomTag, List<StatsEvent> pulledData) { + int pullBuildInformation(int atomTag, List<StatsEvent> pulledData) { StatsEvent e = StatsEvent.newBuilder() .setAtomId(atomTag) .writeString(Build.FINGERPRINT) @@ -2365,13 +2469,13 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullRoleHolder(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } // Add a RoleHolder atom for each package that holds a role. - private int pullRoleHolder(int atomTag, List<StatsEvent> pulledData) { + int pullRoleHolder(int atomTag, List<StatsEvent> pulledData) { long callingToken = Binder.clearCallingIdentity(); try { PackageManager pm = mContext.getPackageManager(); @@ -2423,12 +2527,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullDangerousPermissionState(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullDangerousPermissionState(int atomTag, List<StatsEvent> pulledData) { + int pullDangerousPermissionState(int atomTag, List<StatsEvent> pulledData) { final long token = Binder.clearCallingIdentity(); Set<Integer> reportedUids = new HashSet<>(); try { @@ -2458,7 +2562,7 @@ public class StatsPullAtomService extends SystemService { reportedUids.add(pkg.applicationInfo.uid); if (atomTag == StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED - && ThreadLocalRandom.current().nextFloat() > 0.2f) { + && ThreadLocalRandom.current().nextFloat() > 0.01f) { continue; } @@ -2509,12 +2613,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullTimeZoneDataInfo(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullTimeZoneDataInfo(int atomTag, List<StatsEvent> pulledData) { + int pullTimeZoneDataInfo(int atomTag, List<StatsEvent> pulledData) { String tzDbVersion = "Unknown"; try { tzDbVersion = android.icu.util.TimeZone.getTZDataVersion(); @@ -2536,12 +2640,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullExternalStorageInfo(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullExternalStorageInfo(int atomTag, List<StatsEvent> pulledData) { + int pullExternalStorageInfo(int atomTag, List<StatsEvent> pulledData) { if (mStorageManager == null) { return StatsManager.PULL_SKIP; } @@ -2586,12 +2690,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullAppsOnExternalStorageInfo(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullAppsOnExternalStorageInfo(int atomTag, List<StatsEvent> pulledData) { + int pullAppsOnExternalStorageInfo(int atomTag, List<StatsEvent> pulledData) { if (mStorageManager == null) { return StatsManager.PULL_SKIP; } @@ -2642,12 +2746,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullFaceSettings(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullFaceSettings(int atomTag, List<StatsEvent> pulledData) { + int pullFaceSettings(int atomTag, List<StatsEvent> pulledData) { final long callingToken = Binder.clearCallingIdentity(); try { List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers(); @@ -2696,13 +2800,13 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullAppOps(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullAppOps(int atomTag, List<StatsEvent> pulledData) { + int pullAppOps(int atomTag, List<StatsEvent> pulledData) { final long token = Binder.clearCallingIdentity(); try { AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); @@ -2807,12 +2911,12 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullNotificationRemoteViews(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } - private int pullNotificationRemoteViews(int atomTag, List<StatsEvent> pulledData) { + int pullNotificationRemoteViews(int atomTag, List<StatsEvent> pulledData) { INotificationManager notificationManagerService = getINotificationManagerService(); if (notificationManagerService == null) { return StatsManager.PULL_SKIP; @@ -2851,8 +2955,8 @@ public class StatsPullAtomService extends SystemService { mStatsManager.registerPullAtomCallback( tagId, null, // use default PullAtomMetadata values - (atomTag, data) -> pullDangerousPermissionState(atomTag, data), - BackgroundThread.getExecutor() + BackgroundThread.getExecutor(), + mStatsCallbackImpl ); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 3f7d373c1848..9a30f1de70f0 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -79,7 +79,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private final Context mContext; - private final WindowManagerService mWindowManager; private Handler mHandler = new Handler(); private NotificationDelegate mNotificationDelegate; private volatile IStatusBar mBar; @@ -93,6 +92,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private final Object mLock = new Object(); private final DeathRecipient mDeathRecipient = new DeathRecipient(); private int mCurrentUserId; + private boolean mTracingEnabled; private SparseArray<UiState> mDisplayUiState = new SparseArray<>(); @@ -176,11 +176,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } /** - * Construct the service, add the status bar view to the window manager + * Construct the service */ - public StatusBarManagerService(Context context, WindowManagerService windowManager) { + public StatusBarManagerService(Context context) { mContext = context; - mWindowManager = windowManager; LocalServices.addService(StatusBarManagerInternal.class, mInternalService); LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider); @@ -720,6 +719,31 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } } + @Override + public void startTracing() { + if (mBar != null) { + try { + mBar.startTracing(); + mTracingEnabled = true; + } catch (RemoteException ex) {} + } + } + + @Override + public void stopTracing() { + if (mBar != null) { + try { + mTracingEnabled = false; + mBar.stopTracing(); + } catch (RemoteException ex) {} + } + } + + @Override + public boolean isTracing() { + return mTracingEnabled; + } + // TODO(b/117478341): make it aware of multi-display if needed. @Override public void disable(int what, IBinder token, String pkg) { @@ -1364,6 +1388,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override + public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed) { + enforceStatusBarService(); + long identity = Binder.clearCallingIdentity(); + try { + mNotificationDelegate.onBubbleNotificationSuppressionChanged(key, isNotifSuppressed); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void grantInlineReplyUriPermission(String key, Uri uri, UserHandle user, String packageName) { enforceStatusBarService(); diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java index d7f86cd65ae9..a79c19f7bc17 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java +++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java @@ -72,6 +72,8 @@ public class StatusBarShellCommand extends ShellCommand { return runDisableForSetup(); case "send-disable-flag": return runSendDisableFlag(); + case "tracing": + return runTracing(); default: return handleDefaultCommands(cmd); } @@ -185,6 +187,18 @@ public class StatusBarShellCommand extends ShellCommand { return 0; } + private int runTracing() { + switch (getNextArg()) { + case "start": + mInterface.startTracing(); + break; + case "stop": + mInterface.stopTracing(); + break; + } + return 0; + } + @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); @@ -233,6 +247,9 @@ public class StatusBarShellCommand extends ShellCommand { pw.println(" clock - disable clock appearing in status bar"); pw.println(" notification-icons - disable notification icons from status bar"); pw.println(""); + pw.println(" tracing (start | stop)"); + pw.println(" Start or stop SystemUI tracing"); + pw.println(""); } /** diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java index 9347d2194b68..cdf8ea3460c4 100644 --- a/services/core/java/com/android/server/timezone/RulesManagerService.java +++ b/services/core/java/com/android/server/timezone/RulesManagerService.java @@ -49,7 +49,7 @@ import com.android.timezone.distro.installer.TimeZoneDistroInstaller; import libcore.timezone.TimeZoneDataFiles; import libcore.timezone.TimeZoneFinder; import libcore.timezone.TzDataSetVersion; -import libcore.timezone.ZoneInfoDB; +import libcore.timezone.ZoneInfoDb; import java.io.File; import java.io.FileDescriptor; @@ -518,9 +518,9 @@ public final class RulesManagerService extends IRulesManager.Stub { case 'a': { // Report the active rules version (i.e. the rules in use by the current // process). - pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): " + pw.println("Active rules version (ICU, ZoneInfoDb, TimeZoneFinder): " + TimeZone.getTZDataVersion() + "," - + ZoneInfoDB.getInstance().getVersion() + "," + + ZoneInfoDb.getInstance().getVersion() + "," + TimeZoneFinder.getInstance().getIanaVersion()); break; } @@ -536,7 +536,7 @@ public final class RulesManagerService extends IRulesManager.Stub { pw.println("RulesManagerService state: " + toString()); pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): " + TimeZone.getTZDataVersion() + "," - + ZoneInfoDB.getInstance().getVersion() + "," + + ZoneInfoDb.getInstance().getVersion() + "," + TimeZoneFinder.getInstance().getIanaVersion()); pw.println("Distro state: " + rulesState.toString()); mPackageTracker.dump(pw); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index e8704ab789f2..4f010d5e0f2c 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -1681,7 +1681,8 @@ public final class TvInputManagerService extends SystemService { } @Override - public void startRecording(IBinder sessionToken, @Nullable Uri programUri, int userId) { + public void startRecording(IBinder sessionToken, @Nullable Uri programUri, + @Nullable Bundle params, int userId) { final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, userId, "startRecording"); @@ -1690,7 +1691,7 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { try { getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording( - programUri); + programUri, params); } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in startRecording", e); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 33e18c1263ab..f8f286ace9dd 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -112,16 +112,36 @@ import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_UNSET; import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN; -import static com.android.server.am.ActivityRecordProto.APP_WINDOW_TOKEN; -import static com.android.server.am.ActivityRecordProto.FRONT_OF_TASK; -import static com.android.server.am.ActivityRecordProto.IDENTIFIER; -import static com.android.server.am.ActivityRecordProto.PROC_ID; -import static com.android.server.am.ActivityRecordProto.STATE; -import static com.android.server.am.ActivityRecordProto.TRANSLUCENT; -import static com.android.server.am.ActivityRecordProto.VISIBLE; -import static com.android.server.am.ActivityRecordProto.VISIBLE_REQUESTED; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.ActivityRecordProto.ALL_DRAWN; +import static com.android.server.wm.ActivityRecordProto.APP_STOPPED; +import static com.android.server.wm.ActivityRecordProto.CLIENT_VISIBLE; +import static com.android.server.wm.ActivityRecordProto.DEFER_HIDING_CLIENT; +import static com.android.server.wm.ActivityRecordProto.FILLS_PARENT; +import static com.android.server.wm.ActivityRecordProto.FRONT_OF_TASK; +import static com.android.server.wm.ActivityRecordProto.FROZEN_BOUNDS; +import static com.android.server.wm.ActivityRecordProto.IDENTIFIER; +import static com.android.server.wm.ActivityRecordProto.IS_ANIMATING; +import static com.android.server.wm.ActivityRecordProto.IS_WAITING_FOR_TRANSITION_START; +import static com.android.server.wm.ActivityRecordProto.LAST_ALL_DRAWN; +import static com.android.server.wm.ActivityRecordProto.LAST_SURFACE_SHOWING; +import static com.android.server.wm.ActivityRecordProto.NAME; +import static com.android.server.wm.ActivityRecordProto.NUM_DRAWN_WINDOWS; +import static com.android.server.wm.ActivityRecordProto.NUM_INTERESTING_WINDOWS; +import static com.android.server.wm.ActivityRecordProto.PROC_ID; +import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN; +import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE; +import static com.android.server.wm.ActivityRecordProto.STARTING_DISPLAYED; +import static com.android.server.wm.ActivityRecordProto.STARTING_MOVED; +import static com.android.server.wm.ActivityRecordProto.STARTING_WINDOW; +import static com.android.server.wm.ActivityRecordProto.STATE; +import static com.android.server.wm.ActivityRecordProto.THUMBNAIL; +import static com.android.server.wm.ActivityRecordProto.TRANSLUCENT; +import static com.android.server.wm.ActivityRecordProto.VISIBLE; +import static com.android.server.wm.ActivityRecordProto.VISIBLE_REQUESTED; +import static com.android.server.wm.ActivityRecordProto.VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW; +import static com.android.server.wm.ActivityRecordProto.WINDOW_TOKEN; import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED; import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING; import static com.android.server.wm.ActivityStack.ActivityState.FINISHING; @@ -167,27 +187,6 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_F import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutLocked; -import static com.android.server.wm.AppWindowTokenProto.ALL_DRAWN; -import static com.android.server.wm.AppWindowTokenProto.APP_STOPPED; -import static com.android.server.wm.AppWindowTokenProto.CLIENT_VISIBLE; -import static com.android.server.wm.AppWindowTokenProto.DEFER_HIDING_CLIENT; -import static com.android.server.wm.AppWindowTokenProto.FILLS_PARENT; -import static com.android.server.wm.AppWindowTokenProto.FROZEN_BOUNDS; -import static com.android.server.wm.AppWindowTokenProto.IS_ANIMATING; -import static com.android.server.wm.AppWindowTokenProto.IS_WAITING_FOR_TRANSITION_START; -import static com.android.server.wm.AppWindowTokenProto.LAST_ALL_DRAWN; -import static com.android.server.wm.AppWindowTokenProto.LAST_SURFACE_SHOWING; -import static com.android.server.wm.AppWindowTokenProto.NAME; -import static com.android.server.wm.AppWindowTokenProto.NUM_DRAWN_WINDOWS; -import static com.android.server.wm.AppWindowTokenProto.NUM_INTERESTING_WINDOWS; -import static com.android.server.wm.AppWindowTokenProto.REPORTED_DRAWN; -import static com.android.server.wm.AppWindowTokenProto.REPORTED_VISIBLE; -import static com.android.server.wm.AppWindowTokenProto.STARTING_DISPLAYED; -import static com.android.server.wm.AppWindowTokenProto.STARTING_MOVED; -import static com.android.server.wm.AppWindowTokenProto.STARTING_WINDOW; -import static com.android.server.wm.AppWindowTokenProto.THUMBNAIL; -import static com.android.server.wm.AppWindowTokenProto.VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW; -import static com.android.server.wm.AppWindowTokenProto.WINDOW_TOKEN; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; @@ -7496,7 +7495,36 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * {@code ActivityRecordProto} is the outer-most proto data. */ void dumpDebug(ProtoOutputStream proto) { - dumpDebug(proto, APP_WINDOW_TOKEN, WindowTraceLogLevel.ALL); + writeNameToProto(proto, NAME); + super.dumpDebug(proto, WINDOW_TOKEN, WindowTraceLogLevel.ALL); + proto.write(LAST_SURFACE_SHOWING, mLastSurfaceShowing); + proto.write(IS_WAITING_FOR_TRANSITION_START, isWaitingForTransitionStart()); + proto.write(IS_ANIMATING, isAnimating()); + if (mThumbnail != null) { + mThumbnail.dumpDebug(proto, THUMBNAIL); + } + proto.write(FILLS_PARENT, mOccludesParent); + proto.write(APP_STOPPED, mAppStopped); + proto.write(VISIBLE_REQUESTED, mVisibleRequested); + proto.write(CLIENT_VISIBLE, mClientVisible); + proto.write(DEFER_HIDING_CLIENT, mDeferHidingClient); + proto.write(REPORTED_DRAWN, reportedDrawn); + proto.write(REPORTED_VISIBLE, reportedVisible); + proto.write(NUM_INTERESTING_WINDOWS, mNumInterestingWindows); + proto.write(NUM_DRAWN_WINDOWS, mNumDrawnWindows); + proto.write(ALL_DRAWN, allDrawn); + proto.write(LAST_ALL_DRAWN, mLastAllDrawn); + if (startingWindow != null) { + startingWindow.writeIdentifierToProto(proto, STARTING_WINDOW); + } + proto.write(STARTING_DISPLAYED, startingDisplayed); + proto.write(STARTING_MOVED, startingMoved); + proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW, + mVisibleSetFromTransferredStartingWindow); + for (Rect bounds : mFrozenBounds) { + bounds.dumpDebug(proto, FROZEN_BOUNDS); + } + writeIdentifierToProto(proto, IDENTIFIER); proto.write(STATE, mState.toString()); proto.write(VISIBLE_REQUESTED, mVisibleRequested); @@ -7536,7 +7564,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } proto.write(FILLS_PARENT, mOccludesParent); proto.write(APP_STOPPED, mAppStopped); - proto.write(com.android.server.wm.AppWindowTokenProto.VISIBLE_REQUESTED, mVisibleRequested); + proto.write(VISIBLE_REQUESTED, mVisibleRequested); proto.write(CLIENT_VISIBLE, mClientVisible); proto.write(DEFER_HIDING_CLIENT, mDeferHidingClient); proto.write(REPORTED_DRAWN, reportedDrawn); @@ -7555,7 +7583,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A for (Rect bounds : mFrozenBounds) { bounds.dumpDebug(proto, FROZEN_BOUNDS); } - proto.write(com.android.server.wm.AppWindowTokenProto.VISIBLE, mVisible); + proto.write(VISIBLE, mVisible); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 663423f8728b..ddf0117987dd 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -660,6 +660,8 @@ class ActivityStack extends Task implements BoundsAnimationTarget { super.onConfigurationChanged(newParentConfig); + updateTaskOrganizerState(); + // Only need to update surface size here since the super method will handle updating // surface position. updateSurfaceSize(getPendingTransaction()); @@ -762,6 +764,22 @@ class ActivityStack extends Task implements BoundsAnimationTarget { } } + void updateTaskOrganizerState() { + if (!isRootTask()) { + return; + } + + final int windowingMode = getWindowingMode(); + /* + * Different windowing modes may be managed by different task organizers. If + * getTaskOrganizer returns null, we still call setTaskOrganizer to + * make sure we clear it. + */ + final ITaskOrganizer org = + mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); + setTaskOrganizer(org); + } + @Override public void setWindowingMode(int windowingMode) { // Calling Task#setWindowingMode() for leaf task since this is the a specialization of @@ -774,15 +792,6 @@ class ActivityStack extends Task implements BoundsAnimationTarget { setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, false /* creating */); - windowingMode = getWindowingMode(); - /* - * Different windowing modes may be managed by different task organizers. If - * getTaskOrganizer returns null, we still call transferToTaskOrganizer to - * make sure we clear it. - */ - final ITaskOrganizer org = - mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); - transferToTaskOrganizer(org); } /** @@ -1583,24 +1592,6 @@ class ActivityStack extends Task implements BoundsAnimationTarget { return topActivity != null && topActivity.mVisibleRequested; } - private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) { - tr.setTaskOrganizer(organizer); - } - - /** - * Transfer control of the leashes and IWindowContainers to the given ITaskOrganizer. - * This will (or shortly there-after) invoke the taskAppeared callbacks. - * If the tasks had a previous TaskOrganizer, setTaskOrganizer will take care of - * emitting the taskVanished callbacks. - */ - void transferToTaskOrganizer(ITaskOrganizer organizer) { - final PooledConsumer c = PooledLambda.obtainConsumer( - ActivityStack::transferSingleTaskToOrganizer, - PooledLambda.__(Task.class), organizer); - forAllTasks(c, true /* traverseTopToBottom */, this); - c.recycle(); - } - /** * Returns true if the stack should be visible. * diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 26812f462b3f..9e3292b59402 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1595,11 +1595,11 @@ class ActivityStarter { mRootWindowContainer.resumeFocusedStacksTopActivities( mTargetStack, mStartActivity, mOptions); } - } else if (mStartActivity != null) { - mSupervisor.mRecentTasks.add(mStartActivity.getTask()); } mRootWindowContainer.updateUserStack(mStartActivity.mUserId, mTargetStack); + // Update the recent tasks list immediately when the activity starts + mSupervisor.mRecentTasks.add(mStartActivity.getTask()); mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredWindowingMode, mPreferredDisplayId, mTargetStack); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index bf89bab53f30..c50048eeab64 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -112,7 +112,6 @@ import static com.android.server.wm.DisplayContentProto.ID; import static com.android.server.wm.DisplayContentProto.IME_WINDOWS; import static com.android.server.wm.DisplayContentProto.OPENING_APPS; import static com.android.server.wm.DisplayContentProto.OVERLAY_WINDOWS; -import static com.android.server.wm.DisplayContentProto.PINNED_STACK_CONTROLLER; import static com.android.server.wm.DisplayContentProto.ROTATION; import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION; import static com.android.server.wm.DisplayContentProto.STACKS; @@ -2779,7 +2778,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo stack.dumpDebugInnerStackOnly(proto, STACKS, logLevel); } mDividerControllerLocked.dumpDebug(proto, DOCKED_STACK_DIVIDER_CONTROLLER); - mPinnedStackControllerLocked.dumpDebug(proto, PINNED_STACK_CONTROLLER); for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) { final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i); windowToken.dumpDebug(proto, ABOVE_APP_WINDOWS, logLevel); @@ -4732,7 +4730,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // exists so it get's layered above the starting window. if (imeTarget != null && !(imeTarget.mActivityRecord != null && imeTarget.mActivityRecord.hasStartingWindow()) && ( - !(imeTarget.inSplitScreenWindowingMode() + !(imeTarget.inMultiWindowMode() || imeTarget.mToken.isAppTransitioning()) && ( imeTarget.getSurfaceControl() != null))) { mImeWindowsContainers.assignRelativeLayer(t, imeTarget.getSurfaceControl(), diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index f8df883a3e1c..c6ccd4af06c5 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1014,8 +1014,19 @@ public class DisplayPolicy { mNavigationBarController.setWindow(win); mNavigationBarController.setOnBarVisibilityChangedListener( mNavBarVisibilityListener, true); - mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, - win, null /* frameProvider */); + mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win, + (displayFrames, windowState, inOutFrame) -> { + + // In Gesture Nav, navigation bar frame is larger than frame to + // calculate inset. + if (mNavigationBarPosition == NAV_BAR_BOTTOM) { + sTmpRect.set(displayFrames.mUnrestricted); + sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe); + inOutFrame.top = sTmpRect.bottom + - getNavigationBarHeight(displayFrames.mRotation, + mDisplayContent.getConfiguration().uiMode); + } + }); mDisplayContent.setInsetProvider(ITYPE_BOTTOM_GESTURES, win, (displayFrames, windowState, inOutFrame) -> { inOutFrame.top -= mBottomGestureAdditionalInset; @@ -2747,7 +2758,8 @@ public class DisplayPolicy { mDisplayContent.getDisplayId(), null /* overrideConfig */, uiContext.getResources().getCompatibilityInfo(), - null /* classLoader */); + null /* classLoader */, + null /* loaders */); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 4c5914bfd8c2..faa6e52da8bc 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -161,7 +161,13 @@ final class InputMonitor { void onDisplayRemoved() { mHandler.removeCallbacks(mUpdateInputWindows); - mService.mInputManager.onDisplayRemoved(mDisplayId); + mHandler.post(() -> { + // Make sure any pending setInputWindowInfo transactions are completed. That prevents + // the timing of updating input info of removed display after cleanup. + mService.mTransactionFactory.get().syncInputWindows().apply(); + // It calls InputDispatcher::setInputWindows directly. + mService.mInputManager.onDisplayRemoved(mDisplayId); + }); mDisplayRemoved = true; } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 5a591ecd4746..2bb58ddc5b38 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -233,7 +233,7 @@ class InsetsSourceProvider { // window crop of the surface controls (including the leash) until the client finishes // drawing the new frame of the new orientation. Although we cannot defer the reparent // operation, it is fine, because reparent won't cause any visual effect. - final SurfaceControl barrier = mWin.mWinAnimator.mSurfaceController.mSurfaceControl; + final SurfaceControl barrier = mWin.getDeferTransactionBarrier(); t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber); t.deferTransactionUntil(leash, barrier, frameNumber); } diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index 1d5b5d1a46a4..668b6095738f 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -18,8 +18,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static com.android.server.wm.PinnedStackControllerProto.DEFAULT_BOUNDS; -import static com.android.server.wm.PinnedStackControllerProto.MOVEMENT_BOUNDS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -34,7 +32,6 @@ import android.os.RemoteException; import android.util.DisplayMetrics; import android.util.Log; import android.util.Slog; -import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.IPinnedStackController; import android.view.IPinnedStackListener; @@ -97,11 +94,6 @@ class PinnedStackController { // Temp vars for calculation private final DisplayMetrics mTmpMetrics = new DisplayMetrics(); - // TODO(b/141200935): remove this when we have default/movement bounds tests in SysUI. - // Keep record of the default and movement bounds - private final Rect mLastReportedDefaultBounds = new Rect(); - private final Rect mLastReportedMovementBounds = new Rect(); - /** * The callback object passed to listeners for them to notify the controller of state changes. */ @@ -143,14 +135,6 @@ class PinnedStackController { } } } - - @Override - public void reportBounds(Rect defaultBounds, Rect movementBounds) { - synchronized (mService.mGlobalLock) { - mLastReportedDefaultBounds.set(defaultBounds); - mLastReportedMovementBounds.set(movementBounds); - } - } } /** @@ -421,8 +405,6 @@ class PinnedStackController { void dump(String prefix, PrintWriter pw) { pw.println(prefix + "PinnedStackController"); - pw.println(prefix + " mLastReportedDefaultBounds=" + mLastReportedDefaultBounds); - pw.println(prefix + " mLastReportedMovementBounds=" + mLastReportedMovementBounds); pw.println(prefix + " mDefaultAspectRatio=" + mDefaultAspectRatio); pw.println(prefix + " mIsImeShowing=" + mIsImeShowing); pw.println(prefix + " mImeHeight=" + mImeHeight); @@ -443,11 +425,4 @@ class PinnedStackController { } pw.println(prefix + " mDisplayInfo=" + mDisplayInfo); } - - void dumpDebug(ProtoOutputStream proto, long fieldId) { - final long token = proto.start(fieldId); - mLastReportedDefaultBounds.dumpDebug(proto, DEFAULT_BOUNDS); - mLastReportedMovementBounds.dumpDebug(proto, MOVEMENT_BOUNDS); - proto.end(token); - } } diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 292e8aaa0c04..a9dc36d91a3f 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -173,6 +173,9 @@ class RecentTasks { private final ArrayList<Task> mTasks = new ArrayList<>(); private final ArrayList<Callbacks> mCallbacks = new ArrayList<>(); + /** The non-empty tasks that are removed from recent tasks (see {@link #removeForAddTask}). */ + private final ArrayList<Task> mHiddenTasks = new ArrayList<>(); + // These values are generally loaded from resources, but can be set dynamically in the tests private boolean mHasVisibleRecentTasks; private int mGlobalMaxNumTasks; @@ -1024,6 +1027,12 @@ class RecentTasks { void add(Task task) { if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "add: task=" + task); + // Clean up the hidden tasks when going to home because the user may not be unable to return + // to the task from recents. + if (!mHiddenTasks.isEmpty() && task.isActivityTypeHome()) { + removeUnreachableHiddenTasks(task.getWindowingMode()); + } + final boolean isAffiliated = task.mAffiliatedTaskId != task.mTaskId || task.mNextAffiliateTaskId != INVALID_TASK_ID || task.mPrevAffiliateTaskId != INVALID_TASK_ID; @@ -1390,6 +1399,28 @@ class RecentTasks { return display.getIndexOf(stack) < display.getIndexOf(display.getRootHomeTask()); } + /** Remove the tasks that user may not be able to return. */ + private void removeUnreachableHiddenTasks(int windowingMode) { + for (int i = mHiddenTasks.size() - 1; i >= 0; i--) { + final Task hiddenTask = mHiddenTasks.get(i); + if (!hiddenTask.hasChild()) { + // The task was removed by other path. + mHiddenTasks.remove(i); + continue; + } + if (hiddenTask.getWindowingMode() != windowingMode + || hiddenTask.getTopVisibleActivity() != null) { + // The task may be reachable from the back stack of other windowing mode or it is + // currently in use. Keep the task in the hidden list to avoid losing track, e.g. + // after dismissing primary split screen. + continue; + } + mHiddenTasks.remove(i); + mSupervisor.removeTask(hiddenTask, false /* killProcess */, + !REMOVE_FROM_RECENTS, "remove-hidden-task"); + } + } + /** * If needed, remove oldest existing entries in recents that are for the same kind * of task as the given one. @@ -1406,6 +1437,14 @@ class RecentTasks { // callbacks here. final Task removedTask = mTasks.remove(removeIndex); if (removedTask != task) { + // The added task is in recents so it is not hidden. + mHiddenTasks.remove(task); + if (removedTask.hasChild()) { + // A non-empty task is replaced by a new task. Because the removed task is no longer + // managed by the recent tasks list, add it to the hidden list to prevent the task + // from becoming dangling. + mHiddenTasks.add(removedTask); + } notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */); if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming task=" + removedTask + " for addition of task=" + task); @@ -1662,6 +1701,9 @@ class RecentTasks { pw.println("mFreezeTaskListReordering=" + mFreezeTaskListReordering); pw.println("mFreezeTaskListReorderingPendingTimeout=" + mService.mH.hasCallbacks(mResetFreezeTaskListOnTimeoutRunnable)); + if (!mHiddenTasks.isEmpty()) { + pw.println("mHiddenTasks=" + mHiddenTasks); + } if (mTasks.isEmpty()) { return; } diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java index ba31818d6331..c621c48b0028 100644 --- a/services/core/java/com/android/server/wm/SeamlessRotator.java +++ b/services/core/java/com/android/server/wm/SeamlessRotator.java @@ -101,9 +101,9 @@ public class SeamlessRotator { t.setPosition(win.mSurfaceControl, win.mLastSurfacePosition.x, win.mLastSurfacePosition.y); if (win.mWinAnimator.mSurfaceController != null && !timeout) { t.deferTransactionUntil(win.mSurfaceControl, - win.mWinAnimator.mSurfaceController.mSurfaceControl, win.getFrameNumber()); + win.getDeferTransactionBarrier(), win.getFrameNumber()); t.deferTransactionUntil(win.mWinAnimator.mSurfaceController.mSurfaceControl, - win.mWinAnimator.mSurfaceController.mSurfaceControl, win.getFrameNumber()); + win.getDeferTransactionBarrier(), win.getFrameNumber()); } } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 5babdafc856d..de7f9e41cac0 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -191,7 +191,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize) { + Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); @@ -199,7 +199,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { requestedWidth, requestedHeight, viewFlags, flags, frameNumber, outFrame, outContentInsets, outVisibleInsets, outStableInsets, outBackdropFrame, cutout, - mergedConfiguration, outSurfaceControl, outInsetsState, outSurfaceSize); + mergedConfiguration, outSurfaceControl, outInsetsState, outSurfaceSize, + outBLASTSurfaceControl); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9dba0d3ee051..36cae1fb9b36 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -90,7 +90,7 @@ 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.APP_WINDOW_TOKENS; +import static com.android.server.wm.TaskProto.ACTIVITY; 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; @@ -3171,7 +3171,7 @@ class Task extends WindowContainer<WindowContainer> { super.dumpDebug(proto, WINDOW_CONTAINER, logLevel); proto.write(TaskProto.ID, mTaskId); forAllActivities((r) -> { - r.dumpDebug(proto, APP_WINDOW_TOKENS, logLevel); + r.dumpDebug(proto, ACTIVITY); }); proto.write(FILLS_PARENT, matchParentBounds()); getBounds().dumpDebug(proto, TaskProto.BOUNDS); @@ -3864,9 +3864,8 @@ class Task extends WindowContainer<WindowContainer> { } boolean isControlledByTaskOrganizer() { - // TODO(b/147849315): Clean-up relationship between task-org and task-hierarchy. Ideally - // we only give control of the root task. - return getTopMostTask().mTaskOrganizer != null; + final Task rootTask = getRootTask(); + return rootTask == this && rootTask.mTaskOrganizer != null; } @Override @@ -3938,23 +3937,6 @@ class Task extends WindowContainer<WindowContainer> { super.getRelativeDisplayedPosition(outPos); } - @Override - public void setWindowingMode(int windowingMode) { - super.setWindowingMode(windowingMode); - windowingMode = getWindowingMode(); - - // TODO(b/147849315): Clean-up relationship between task-org and task-hierarchy. Ideally - // we only give control of the root task. - // Different windowing modes may be managed by different task organizers. If - // getTaskOrganizer returns null, we still call transferToTaskOrganizer to make sure we - // clear it. - if (!isRootTask()) { - final ITaskOrganizer org = - mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); - setTaskOrganizer(org); - } - } - /** * @return true if the task is currently focused. */ diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index 57de7536409f..e47eaee5bce4 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -116,7 +116,11 @@ class TaskSnapshotSurface implements StartingSurface { private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotStartingWindow" : TAG_WM; private static final int MSG_REPORT_DRAW = 0; private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s"; - private static final Point sSurfaceSize = new Point(); //tmp var for unused relayout param + + //tmp vars for unused relayout params + private static final Point sTmpSurfaceSize = new Point(); + private static final SurfaceControl sTmpSurfaceControl = new SurfaceControl(); + private final Window mWindow; private final Surface mSurface; private SurfaceControl mSurfaceControl; @@ -230,7 +234,7 @@ class TaskSnapshotSurface implements StartingSurface { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1, tmpFrame, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState, - sSurfaceSize); + sTmpSurfaceSize, sTmpSurfaceControl); } catch (RemoteException e) { // Local call. } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index e13083007237..2c6c756dda3b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2071,7 +2071,7 @@ public class WindowManagerService extends IWindowManager.Stub Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize) { + Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { int result = 0; boolean configChanged; final int pid = Binder.getCallingPid(); @@ -2238,7 +2238,8 @@ public class WindowManagerService extends IWindowManager.Stub result = win.relayoutVisibleWindow(result, attrChanges); try { - result = createSurfaceControl(outSurfaceControl, result, win, winAnimator); + result = createSurfaceControl(outSurfaceControl, outBLASTSurfaceControl, + result, win, winAnimator); } catch (Exception e) { displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/); @@ -2270,6 +2271,7 @@ public class WindowManagerService extends IWindowManager.Stub // surface, let the client use that, but don't create new surface at this point. Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface"); winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl); + winAnimator.mSurfaceController.getBLASTSurfaceControl(outBLASTSurfaceControl); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } else { if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win); @@ -2451,7 +2453,8 @@ public class WindowManagerService extends IWindowManager.Stub } private int createSurfaceControl(SurfaceControl outSurfaceControl, - int result, WindowState win, WindowStateAnimator winAnimator) { + SurfaceControl outBLASTSurfaceControl, int result, + WindowState win, WindowStateAnimator winAnimator) { if (!win.mHasSurface) { result |= RELAYOUT_RES_SURFACE_CHANGED; } @@ -2465,6 +2468,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (surfaceController != null) { surfaceController.getSurfaceControl(outSurfaceControl); + surfaceController.getBLASTSurfaceControl(outBLASTSurfaceControl); ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl); } else { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 73984fd49f73..b9694c3b9bd3 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5616,4 +5616,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean isNonToastWindowVisibleForPid(int pid) { return mSession.mPid == pid && isNonToastOrStarting() && isVisibleNow(); } + + SurfaceControl getDeferTransactionBarrier() { + return mWinAnimator.getDeferTransactionBarrier(); + } } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 069ee4fea8ec..9552df7b5899 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1010,7 +1010,7 @@ class WindowStateAnimator { // the WS position is reset (so the stack position is shown) at the same // time that the buffer size changes. setOffsetPositionForStackResize(false); - mSurfaceController.deferTransactionUntil(mSurfaceController.mSurfaceControl, + mSurfaceController.deferTransactionUntil(mWin.getDeferTransactionBarrier(), mWin.getFrameNumber()); } else { final ActivityStack stack = mWin.getRootTask(); @@ -1041,7 +1041,7 @@ class WindowStateAnimator { // comes in at the new size (normally position and crop are unfrozen). // deferTransactionUntil accomplishes this for us. if (wasForceScaled && !mForceScaleUntilResize) { - mSurfaceController.deferTransactionUntil(mSurfaceController.mSurfaceControl, + mSurfaceController.deferTransactionUntil(mWin.getDeferTransactionBarrier(), mWin.getFrameNumber()); mSurfaceController.forceScaleableInTransaction(false); } @@ -1518,4 +1518,11 @@ class WindowStateAnimator { void setOffsetPositionForStackResize(boolean offsetPositionForStackResize) { mOffsetPositionForStackResize = offsetPositionForStackResize; } + + SurfaceControl getDeferTransactionBarrier() { + if (!hasSurface()) { + return null; + } + return mSurfaceController.getDeferTransactionBarrier(); + } } diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 5b8015be1a7a..383c0d9ab3d4 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -51,6 +51,11 @@ class WindowSurfaceController { SurfaceControl mSurfaceControl; + /** + * WM only uses for deferred transactions. + */ + SurfaceControl mBLASTSurfaceControl; + // Should only be set from within setShown(). private boolean mSurfaceShown = false; private float mSurfaceX = 0; @@ -110,13 +115,22 @@ class WindowSurfaceController { .setMetadata(METADATA_WINDOW_TYPE, windowType) .setMetadata(METADATA_OWNER_UID, ownerUid); - if ((win.getAttrs().privateFlags & - WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0) { + final boolean useBLAST = (win.getAttrs().privateFlags & + WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0; + if (useBLAST) { b.setContainerLayer(); } - mSurfaceControl = b.build(); + + if (useBLAST) { + mBLASTSurfaceControl = win.makeSurface() + .setParent(mSurfaceControl) + .setName("BLAST Adapter Layer") + .setBLASTLayer() + .build(); + } + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -168,6 +182,9 @@ class WindowSurfaceController { } finally { setShown(false); mSurfaceControl = null; + if (mBLASTSurfaceControl != null) { + mBLASTSurfaceControl.release(); + } } } @@ -474,6 +491,12 @@ class WindowSurfaceController { outSurfaceControl.copyFrom(mSurfaceControl); } + void getBLASTSurfaceControl(SurfaceControl outSurfaceControl) { + if (mBLASTSurfaceControl != null) { + outSurfaceControl.copyFrom(mBLASTSurfaceControl); + } + } + int getLayer() { return mSurfaceLayer; } @@ -510,6 +533,13 @@ class WindowSurfaceController { return mSurfaceH; } + SurfaceControl getDeferTransactionBarrier() { + if (mBLASTSurfaceControl != null) { + return mBLASTSurfaceControl; + } + return mSurfaceControl; + } + void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(SHOWN, mSurfaceShown); diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index 0be90fcb2b84..ba3dc607f6cc 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -35,7 +35,7 @@ import android.util.proto.ProtoOutputStream; import android.view.Choreographer; import com.android.server.protolog.ProtoLogImpl; -import com.android.server.utils.TraceBuffer; +import com.android.internal.util.TraceBuffer; import java.io.File; import java.io.IOException; @@ -64,7 +64,7 @@ class WindowTracing { private final Object mEnabledLock = new Object(); private final File mTraceFile; - private final com.android.server.utils.TraceBuffer mBuffer; + private final TraceBuffer mBuffer; private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) -> log("onFrame" /* where */); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 77d814e3076b..4a2636ee6ca4 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -14,6 +14,7 @@ cc_library_static { srcs: [ ":graphicsstats_proto", + ":lib_alarmManagerService_native", "BroadcastRadio/JavaRef.cpp", "BroadcastRadio/NativeCallbackThread.cpp", "BroadcastRadio/BroadcastRadioService.cpp", @@ -21,7 +22,6 @@ cc_library_static { "BroadcastRadio/TunerCallback.cpp", "BroadcastRadio/convert.cpp", "BroadcastRadio/regions.cpp", - "com_android_server_AlarmManagerService.cpp", "com_android_server_am_BatteryStatsService.cpp", "com_android_server_connectivity_Vpn.cpp", "com_android_server_ConsumerIrService.cpp", @@ -182,3 +182,10 @@ filegroup { "com_android_server_net_NetworkStatsFactory.cpp", ], } + +filegroup { + name: "lib_alarmManagerService_native", + srcs: [ + "com_android_server_AlarmManagerService.cpp", + ], +} diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp index 6811e6d0e6f2..5a8e25e4cf1c 100644 --- a/services/core/jni/com_android_server_VibratorService.cpp +++ b/services/core/jni/com_android_server_VibratorService.cpp @@ -49,6 +49,12 @@ namespace android { static jmethodID sMethodIdOnComplete; +static struct { + jfieldID id; + jfieldID scale; + jfieldID delay; +} gPrimitiveClassInfo; + static_assert(static_cast<uint8_t>(V1_0::EffectStrength::LIGHT) == static_cast<uint8_t>(aidl::EffectStrength::LIGHT)); static_assert(static_cast<uint8_t>(V1_0::EffectStrength::MEDIUM) == @@ -333,6 +339,21 @@ static void vibratorSetExternalControl(JNIEnv*, jclass, jboolean enabled) { } } +static jintArray vibratorGetSupportedEffects(JNIEnv *env, jclass) { + if (auto hal = getHal<aidl::IVibrator>()) { + std::vector<aidl::Effect> supportedEffects; + if (!hal->call(&aidl::IVibrator::getSupportedEffects, &supportedEffects).isOk()) { + return nullptr; + } + jintArray arr = env->NewIntArray(supportedEffects.size()); + env->SetIntArrayRegion(arr, 0, supportedEffects.size(), + reinterpret_cast<jint*>(supportedEffects.data())); + return arr; + } else { + return nullptr; + } +} + static jlong vibratorPerformEffect(JNIEnv* env, jclass, jlong effect, jlong strength, jobject vibration) { if (auto hal = getHal<aidl::IVibrator>()) { @@ -398,6 +419,37 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass, jlong effect, jlong stre return -1; } +static aidl::CompositeEffect effectFromJavaPrimitive(JNIEnv* env, jobject primitive) { + aidl::CompositeEffect effect; + effect.primitive = static_cast<aidl::CompositePrimitive>( + env->GetIntField(primitive, gPrimitiveClassInfo.id)); + effect.scale = static_cast<float>(env->GetFloatField(primitive, gPrimitiveClassInfo.scale)); + effect.delayMs = static_cast<int>(env->GetIntField(primitive, gPrimitiveClassInfo.delay)); + return effect; +} + +static void vibratorPerformComposedEffect(JNIEnv* env, jclass, jobjectArray composition, + jobject vibration) { + auto hal = getHal<aidl::IVibrator>(); + if (!hal) { + return; + } + size_t size = env->GetArrayLength(composition); + std::vector<aidl::CompositeEffect> effects; + for (size_t i = 0; i < size; i++) { + jobject element = env->GetObjectArrayElement(composition, i); + effects.push_back(effectFromJavaPrimitive(env, element)); + } + sp<AidlVibratorCallback> effectCallback = new AidlVibratorCallback(env, vibration); + + auto status = hal->call(&aidl::IVibrator::compose, effects, effectCallback); + if (!status.isOk()) { + if (status.exceptionCode() != binder::Status::EX_UNSUPPORTED_OPERATION) { + ALOGE("Failed to play haptic effect composition"); + } + } +} + static jlong vibratorGetCapabilities(JNIEnv*, jclass) { if (auto hal = getHal<aidl::IVibrator>()) { int32_t cap = 0; @@ -433,7 +485,12 @@ static const JNINativeMethod method_table[] = { { "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl}, { "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude}, { "vibratorPerformEffect", "(JJLcom/android/server/VibratorService$Vibration;)J", - (void*)vibratorPerformEffect}, + (void*)vibratorPerformEffect}, + { "vibratorPerformComposedEffect", + "([Landroid/os/VibrationEffect$Composition$PrimitiveEffect;Lcom/android/server/VibratorService$Vibration;)V", + (void*)vibratorPerformComposedEffect}, + { "vibratorGetSupportedEffects", "()[I", + (void*)vibratorGetSupportedEffects}, { "vibratorSupportsExternalControl", "()Z", (void*)vibratorSupportsExternalControl}, { "vibratorSetExternalControl", "(Z)V", (void*)vibratorSetExternalControl}, { "vibratorGetCapabilities", "()J", (void*)vibratorGetCapabilities}, @@ -441,11 +498,15 @@ static const JNINativeMethod method_table[] = { { "vibratorAlwaysOnDisable", "(J)V", (void*)vibratorAlwaysOnDisable}, }; -int register_android_server_VibratorService(JNIEnv *env) -{ +int register_android_server_VibratorService(JNIEnv *env) { sMethodIdOnComplete = GetMethodIDOrDie(env, FindClassOrDie(env, "com/android/server/VibratorService$Vibration"), "onComplete", "()V"); + jclass primitiveClass = FindClassOrDie(env, + "android/os/VibrationEffect$Composition$PrimitiveEffect"); + gPrimitiveClassInfo.id = GetFieldIDOrDie(env, primitiveClass, "id", "I"); + gPrimitiveClassInfo.scale = GetFieldIDOrDie(env, primitiveClass, "scale", "F"); + gPrimitiveClassInfo.delay = GetFieldIDOrDie(env, primitiveClass, "delay", "I"); return jniRegisterNativeMethods(env, "com/android/server/VibratorService", method_table, NELEM(method_table)); } diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp index ddf4dd53d5d3..323e7f156941 100644 --- a/services/incremental/Android.bp +++ b/services/incremental/Android.bp @@ -47,6 +47,8 @@ cc_defaults { shared_libs: [ "libandroidfw", "libbinder", + "libcrypto", + "libcutils", "libincfs", "liblog", "libz", diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index bb26c1f93159..f1b637f516ea 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -46,10 +46,10 @@ static bool incFsEnabled() { return incfs::enabled(); } -static bool incFsVersionValid(const sp<IVold>& vold) { - int version = -1; - auto status = vold->incFsVersion(&version); - if (!status.isOk() || version <= 0) { +static bool incFsValid(const sp<IVold>& vold) { + bool enabled = false; + auto status = vold->incFsEnabled(&enabled); + if (!status.isOk() || !enabled) { return false; } return true; @@ -74,7 +74,7 @@ BinderIncrementalService* BinderIncrementalService::start() { return nullptr; } sp<IVold> vold = interface_cast<IVold>(voldBinder); - if (!incFsVersionValid(vold)) { + if (!incFsValid(vold)) { return nullptr; } @@ -86,6 +86,7 @@ BinderIncrementalService* BinderIncrementalService::start() { sp<ProcessState> ps(ProcessState::self()); ps->startThreadPool(); ps->giveThreadPoolName(); + // sm->addService increments the reference count, and now we're OK with returning the pointer. return self.get(); } @@ -107,9 +108,9 @@ binder::Status BinderIncrementalService::openStorage(const std::string& path, return ok(); } -binder::Status BinderIncrementalService::createStorage( - const std::string& path, const DataLoaderParamsParcel& params, - int32_t createMode, int32_t* _aidl_return) { +binder::Status BinderIncrementalService::createStorage(const std::string& path, + const DataLoaderParamsParcel& params, + int32_t createMode, int32_t* _aidl_return) { *_aidl_return = mImpl.createStorage(path, const_cast<DataLoaderParamsParcel&&>(params), android::incremental::IncrementalService::CreateOptions( @@ -129,10 +130,10 @@ binder::Status BinderIncrementalService::createLinkedStorage(const std::string& } binder::Status BinderIncrementalService::makeBindMount(int32_t storageId, - const std::string& pathUnderStorage, + const std::string& sourcePath, const std::string& targetFullPath, int32_t bindType, int32_t* _aidl_return) { - *_aidl_return = mImpl.bind(storageId, pathUnderStorage, targetFullPath, + *_aidl_return = mImpl.bind(storageId, sourcePath, targetFullPath, android::incremental::IncrementalService::BindKind(bindType)); return ok(); } @@ -149,75 +150,127 @@ binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) { return ok(); } -binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, - const std::string& pathUnderStorage, +binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, const std::string& path, int32_t* _aidl_return) { - auto inode = mImpl.makeDir(storageId, pathUnderStorage); - *_aidl_return = inode < 0 ? inode : 0; + *_aidl_return = mImpl.makeDir(storageId, path); return ok(); } -binder::Status BinderIncrementalService::makeDirectories(int32_t storageId, - const std::string& pathUnderStorage, - int32_t* _aidl_return) { - auto inode = mImpl.makeDirs(storageId, pathUnderStorage); - *_aidl_return = inode < 0 ? inode : 0; - return ok(); +static std::tuple<int, incfs::FileId, incfs::NewFileParams> toMakeFileParams( + const android::os::incremental::IncrementalNewFileParams& params) { + incfs::FileId id; + if (params.fileId.empty()) { + if (params.metadata.empty()) { + return {EINVAL, {}, {}}; + } + id = IncrementalService::idFromMetadata(params.metadata); + } else if (params.fileId.size() != sizeof(id)) { + return {EINVAL, {}, {}}; + } else { + memcpy(&id, params.fileId.data(), sizeof(id)); + } + incfs::NewFileParams nfp; + nfp.size = params.size; + nfp.metadata = {(const char*)params.metadata.data(), (IncFsSize)params.metadata.size()}; + if (!params.signature) { + nfp.verification = {}; + } else { + nfp.verification.hashAlgorithm = IncFsHashAlgortithm(params.signature->hashAlgorithm); + nfp.verification.rootHash = {(const char*)params.signature->rootHash.data(), + (IncFsSize)params.signature->rootHash.size()}; + nfp.verification.additionalData = {(const char*)params.signature->additionalData.data(), + (IncFsSize)params.signature->additionalData.size()}; + nfp.verification.signature = {(const char*)params.signature->signature.data(), + (IncFsSize)params.signature->signature.size()}; + } + return {0, id, nfp}; } -binder::Status BinderIncrementalService::makeFile(int32_t storageId, - const std::string& pathUnderStorage, int64_t size, - const std::vector<uint8_t>& metadata, - int32_t* _aidl_return) { - auto inode = mImpl.makeFile(storageId, pathUnderStorage, size, - {(const char*)metadata.data(), metadata.size()}, {}); - *_aidl_return = inode < 0 ? inode : 0; +binder::Status BinderIncrementalService::makeFile( + int32_t storageId, const std::string& path, + const ::android::os::incremental::IncrementalNewFileParams& params, int32_t* _aidl_return) { + auto [err, fileId, nfp] = toMakeFileParams(params); + if (err) { + *_aidl_return = err; + return ok(); + } + + *_aidl_return = mImpl.makeFile(storageId, path, 0555, fileId, nfp); return ok(); } -binder::Status BinderIncrementalService::makeFileFromRange( - int32_t storageId, const std::string& pathUnderStorage, - const std::string& sourcePathUnderStorage, int64_t start, int64_t end, - int32_t* _aidl_return) { +binder::Status BinderIncrementalService::makeFileFromRange(int32_t storageId, + const std::string& targetPath, + const std::string& sourcePath, + int64_t start, int64_t end, + int32_t* _aidl_return) { // TODO(b/136132412): implement this - *_aidl_return = -1; + *_aidl_return = ENOSYS; // not implemented return ok(); } + binder::Status BinderIncrementalService::makeLink(int32_t sourceStorageId, - const std::string& relativeSourcePath, + const std::string& sourcePath, int32_t destStorageId, - const std::string& relativeDestPath, + const std::string& destPath, int32_t* _aidl_return) { - auto sourceInode = mImpl.nodeFor(sourceStorageId, relativeSourcePath); - auto [targetParentInode, name] = mImpl.parentAndNameFor(destStorageId, relativeDestPath); - *_aidl_return = mImpl.link(sourceStorageId, sourceInode, targetParentInode, name); + *_aidl_return = mImpl.link(sourceStorageId, sourcePath, destStorageId, destPath); return ok(); } -binder::Status BinderIncrementalService::unlink(int32_t storageId, - const std::string& pathUnderStorage, + +binder::Status BinderIncrementalService::unlink(int32_t storageId, const std::string& path, int32_t* _aidl_return) { - auto [parentNode, name] = mImpl.parentAndNameFor(storageId, pathUnderStorage); - *_aidl_return = mImpl.unlink(storageId, parentNode, name); + *_aidl_return = mImpl.unlink(storageId, path); return ok(); } + binder::Status BinderIncrementalService::isFileRangeLoaded(int32_t storageId, - const std::string& relativePath, - int64_t start, int64_t end, - bool* _aidl_return) { + const std::string& path, int64_t start, + int64_t end, bool* _aidl_return) { + // TODO: implement *_aidl_return = false; return ok(); } -binder::Status BinderIncrementalService::getFileMetadata(int32_t storageId, - const std::string& relativePath, + +binder::Status BinderIncrementalService::getMetadataByPath(int32_t storageId, + const std::string& path, + std::vector<uint8_t>* _aidl_return) { + auto fid = mImpl.nodeFor(storageId, path); + if (fid != kIncFsInvalidFileId) { + auto metadata = mImpl.getMetadata(storageId, fid); + _aidl_return->assign(metadata.begin(), metadata.end()); + } + return ok(); +} + +static FileId toFileId(const std::vector<uint8_t>& id) { + FileId fid; + memcpy(&fid, id.data(), id.size()); + return fid; +} + +binder::Status BinderIncrementalService::getMetadataById(int32_t storageId, + const std::vector<uint8_t>& id, std::vector<uint8_t>* _aidl_return) { - auto inode = mImpl.nodeFor(storageId, relativePath); - auto metadata = mImpl.getMetadata(storageId, inode); + if (id.size() != sizeof(incfs::FileId)) { + return ok(); + } + auto fid = toFileId(id); + auto metadata = mImpl.getMetadata(storageId, fid); _aidl_return->assign(metadata.begin(), metadata.end()); return ok(); } + +binder::Status BinderIncrementalService::makeDirectories(int32_t storageId, const std::string& path, + int32_t* _aidl_return) { + *_aidl_return = mImpl.makeDirs(storageId, path); + return ok(); +} + binder::Status BinderIncrementalService::startLoading(int32_t storageId, bool* _aidl_return) { *_aidl_return = mImpl.startLoading(storageId); return ok(); } + } // namespace android::os::incremental jlong Incremental_IncrementalService_Start() { diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index 37c9661db28d..a94a75a26875 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -37,38 +37,39 @@ public: void onSystemReady(); void onInvalidStorage(int mountId); - binder::Status openStorage(const std::string &path, int32_t *_aidl_return) final; - binder::Status createStorage( - const std::string &path, - const ::android::content::pm::DataLoaderParamsParcel ¶ms, - int32_t createMode, int32_t *_aidl_return) final; - binder::Status createLinkedStorage(const std::string &path, int32_t otherStorageId, - int32_t createMode, int32_t *_aidl_return) final; - binder::Status makeBindMount(int32_t storageId, const std::string &pathUnderStorage, - const std::string &targetFullPath, int32_t bindType, - int32_t *_aidl_return) final; - binder::Status deleteBindMount(int32_t storageId, const std::string &targetFullPath, - int32_t *_aidl_return) final; + binder::Status openStorage(const std::string& path, int32_t* _aidl_return) final; + binder::Status createStorage(const std::string& path, + const ::android::content::pm::DataLoaderParamsParcel& params, + int32_t createMode, int32_t* _aidl_return) final; + binder::Status createLinkedStorage(const std::string& path, int32_t otherStorageId, + int32_t createMode, int32_t* _aidl_return) final; + binder::Status makeBindMount(int32_t storageId, const std::string& sourcePath, + const std::string& targetFullPath, int32_t bindType, + int32_t* _aidl_return) final; + binder::Status deleteBindMount(int32_t storageId, const std::string& targetFullPath, + int32_t* _aidl_return) final; + binder::Status makeDirectory(int32_t storageId, const std::string& path, + int32_t* _aidl_return) final; + binder::Status makeDirectories(int32_t storageId, const std::string& path, + int32_t* _aidl_return) final; + binder::Status makeFile(int32_t storageId, const std::string& path, + const ::android::os::incremental::IncrementalNewFileParams& params, + int32_t* _aidl_return) final; + binder::Status makeFileFromRange(int32_t storageId, const std::string& targetPath, + const std::string& sourcePath, int64_t start, int64_t end, + int32_t* _aidl_return) final; + binder::Status makeLink(int32_t sourceStorageId, const std::string& sourcePath, + int32_t destStorageId, const std::string& destPath, + int32_t* _aidl_return) final; + binder::Status unlink(int32_t storageId, const std::string& path, int32_t* _aidl_return) final; + binder::Status isFileRangeLoaded(int32_t storageId, const std::string& path, int64_t start, + int64_t end, bool* _aidl_return) final; + binder::Status getMetadataByPath(int32_t storageId, const std::string& path, + std::vector<uint8_t>* _aidl_return) final; + binder::Status getMetadataById(int32_t storageId, const std::vector<uint8_t>& id, + 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 makeDirectory(int32_t storageId, const std::string &pathUnderStorage, - int32_t *_aidl_return) final; - binder::Status makeDirectories(int32_t storageId, const std::string &pathUnderStorage, - int32_t *_aidl_return) final; - binder::Status makeFile(int32_t storageId, const std::string &pathUnderStorage, int64_t size, - const std::vector<uint8_t> &metadata, int32_t *_aidl_return) final; - binder::Status makeFileFromRange(int32_t storageId, const std::string &pathUnderStorage, - const std::string &sourcePathUnderStorage, int64_t start, - int64_t end, int32_t *_aidl_return); - binder::Status makeLink(int32_t sourceStorageId, const std::string &relativeSourcePath, - int32_t destStorageId, const std::string &relativeDestPath, - int32_t *_aidl_return) final; - binder::Status unlink(int32_t storageId, const std::string &pathUnderStorage, - int32_t *_aidl_return) final; - binder::Status isFileRangeLoaded(int32_t storageId, const std::string &relativePath, - int64_t start, int64_t end, bool *_aidl_return) final; - binder::Status getFileMetadata(int32_t storageId, const std::string &relativePath, - std::vector<uint8_t> *_aidl_return) final; - binder::Status startLoading(int32_t storageId, bool *_aidl_return) final; private: android::incremental::IncrementalService mImpl; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index afce260ed41e..414c66c866db 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -30,6 +30,8 @@ #include <binder/BinderService.h> #include <binder/ParcelFileDescriptor.h> #include <binder/Status.h> + +#include <openssl/sha.h> #include <sys/stat.h> #include <uuid/uuid.h> #include <zlib.h> @@ -55,7 +57,6 @@ using IncrementalFileSystemControlParcel = struct Constants { static constexpr auto backing = "backing_store"sv; static constexpr auto mount = "mount"sv; - static constexpr auto image = "incfs.img"sv; static constexpr auto storagePrefix = "st"sv; static constexpr auto mountpointMdPrefix = ".mountpoint."sv; static constexpr auto infoMdName = ".info"sv; @@ -70,7 +71,7 @@ template <base::LogSeverity level = base::ERROR> bool mkdirOrLog(std::string_view name, int mode = 0770, bool allowExisting = true) { auto cstr = path::c_str(name); if (::mkdir(cstr, mode)) { - if (errno != EEXIST) { + if (!allowExisting || errno != EEXIST) { PLOG(level) << "Can't create directory '" << name << '\''; return false; } @@ -80,6 +81,11 @@ bool mkdirOrLog(std::string_view name, int mode = 0770, bool allowExisting = tru return false; } } + if (::chmod(cstr, mode)) { + PLOG(level) << "Changing permission failed for '" << name << '\''; + return false; + } + return true; } @@ -106,7 +112,7 @@ static std::pair<std::string, std::string> makeMountDir(std::string_view increme for (int counter = 0; counter < 1000; mountKey.resize(prefixSize), base::StringAppendF(&mountKey, "%d", counter++)) { auto mountRoot = path::join(incrementalDir, mountKey); - if (mkdirOrLog(mountRoot, 0770, false)) { + if (mkdirOrLog(mountRoot, 0777, false)) { return {mountKey, mountRoot}; } } @@ -116,11 +122,7 @@ static std::pair<std::string, std::string> makeMountDir(std::string_view increme template <class ProtoMessage, class Control> static ProtoMessage parseFromIncfs(const IncFsWrapper* incfs, Control&& control, std::string_view path) { - struct stat st; - if (::stat(path::c_str(path), &st)) { - return {}; - } - auto md = incfs->getMetadata(control, st.st_ino); + auto md = incfs->getMetadata(control, path); ProtoMessage message; return message.ParseFromArray(md.data(), md.size()) ? message : ProtoMessage{}; } @@ -161,35 +163,66 @@ IncrementalService::IncFsMount::~IncFsMount() { } auto IncrementalService::IncFsMount::makeStorage(StorageId id) -> StorageMap::iterator { - metadata::Storage st; - st.set_id(id); - auto metadata = st.SerializeAsString(); - std::string name; for (int no = nextStorageDirNo.fetch_add(1, std::memory_order_relaxed), i = 0; i < 1024 && no >= 0; no = nextStorageDirNo.fetch_add(1, std::memory_order_relaxed), ++i) { name.clear(); - base::StringAppendF(&name, "%.*s%d", int(constants().storagePrefix.size()), - constants().storagePrefix.data(), no); - if (auto node = - incrementalService.mIncFs->makeDir(control, name, INCFS_ROOT_INODE, metadata); - node >= 0) { + base::StringAppendF(&name, "%.*s_%d_%d", int(constants().storagePrefix.size()), + constants().storagePrefix.data(), id, no); + auto fullName = path::join(root, constants().mount, name); + if (auto err = incrementalService.mIncFs->makeDir(control, fullName); !err) { std::lock_guard l(lock); - return storages.insert_or_assign(id, Storage{std::move(name), node}).first; + return storages.insert_or_assign(id, Storage{std::move(fullName)}).first; + } else if (err != EEXIST) { + LOG(ERROR) << __func__ << "(): failed to create dir |" << fullName << "| " << err; + break; } } nextStorageDirNo = 0; return storages.end(); } +static std::unique_ptr<DIR, decltype(&::closedir)> openDir(const char* path) { + return {::opendir(path), ::closedir}; +} + +static int rmDirContent(const char* path) { + auto dir = openDir(path); + if (!dir) { + return -EINVAL; + } + while (auto entry = ::readdir(dir.get())) { + if (entry->d_name == "."sv || entry->d_name == ".."sv) { + continue; + } + auto fullPath = android::base::StringPrintf("%s/%s", path, entry->d_name); + if (entry->d_type == DT_DIR) { + if (const auto err = rmDirContent(fullPath.c_str()); err != 0) { + PLOG(WARNING) << "Failed to delete " << fullPath << " content"; + return err; + } + if (const auto err = ::rmdir(fullPath.c_str()); err != 0) { + PLOG(WARNING) << "Failed to rmdir " << fullPath; + return err; + } + } else { + if (const auto err = ::unlink(fullPath.c_str()); err != 0) { + PLOG(WARNING) << "Failed to delete " << fullPath; + return err; + } + } + } + return 0; +} + void IncrementalService::IncFsMount::cleanupFilesystem(std::string_view root) { - ::unlink(path::join(root, constants().backing, constants().image).c_str()); + rmDirContent(path::join(root, constants().backing).c_str()); ::rmdir(path::join(root, constants().backing).c_str()); ::rmdir(path::join(root, constants().mount).c_str()); ::rmdir(path::c_str(root)); } -IncrementalService::IncrementalService(const ServiceManagerWrapper& sm, std::string_view rootDir) +IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir) : mVold(sm.getVoldService()), mIncrementalManager(sm.getIncrementalManager()), mIncFs(sm.getIncFs()), @@ -205,6 +238,23 @@ IncrementalService::IncrementalService(const ServiceManagerWrapper& sm, std::str // mountExistingImages(); } +FileId IncrementalService::idFromMetadata(std::span<const uint8_t> metadata) { + incfs::FileId id = {}; + if (size_t(metadata.size()) <= sizeof(id)) { + memcpy(&id, metadata.data(), metadata.size()); + } else { + uint8_t buffer[SHA_DIGEST_LENGTH]; + static_assert(sizeof(buffer) >= sizeof(id)); + + SHA_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, metadata.data(), metadata.size()); + SHA1_Final(buffer, &ctx); + memcpy(&id, buffer, sizeof(id)); + } + return id; +} + IncrementalService::~IncrementalService() = default; std::optional<std::future<void>> IncrementalService::onSystemReady() { @@ -300,26 +350,36 @@ StorageId IncrementalService::createStorage(std::string_view mountPoint, std::unique_ptr<std::string, decltype(firstCleanup)>(&mountRoot, firstCleanup); auto mountTarget = path::join(mountRoot, constants().mount); - if (!mkdirOrLog(path::join(mountRoot, constants().backing)) || !mkdirOrLog(mountTarget)) { + const auto backing = path::join(mountRoot, constants().backing); + if (!mkdirOrLog(backing, 0777) || !mkdirOrLog(mountTarget)) { return kInvalidStorageId; } - const auto image = path::join(mountRoot, constants().backing, constants().image); IncFsMount::Control control; { std::lock_guard l(mMountOperationLock); IncrementalFileSystemControlParcel controlParcel; - auto status = mVold->mountIncFs(image, mountTarget, incfs::truncate, &controlParcel); + + if (auto err = rmDirContent(backing.c_str())) { + LOG(ERROR) << "Coudn't clean the backing directory " << backing << ": " << err; + return kInvalidStorageId; + } + if (!mkdirOrLog(path::join(backing, ".index"), 0777)) { + return kInvalidStorageId; + } + auto status = mVold->mountIncFs(backing, mountTarget, 0, &controlParcel); if (!status.isOk()) { LOG(ERROR) << "Vold::mountIncFs() failed: " << status.toString8(); return kInvalidStorageId; } - if (!controlParcel.cmd || !controlParcel.log) { + if (controlParcel.cmd.get() < 0 || controlParcel.pendingReads.get() < 0 || + controlParcel.log.get() < 0) { LOG(ERROR) << "Vold::mountIncFs() returned invalid control parcel."; return kInvalidStorageId; } - control.cmdFd = controlParcel.cmd->release(); - control.logFd = controlParcel.log->release(); + control.cmd = controlParcel.cmd.release().release(); + control.pendingReads = controlParcel.pendingReads.release().release(); + control.logs = controlParcel.log.release().release(); } std::unique_lock l(mLock); @@ -344,7 +404,7 @@ StorageId IncrementalService::createStorage(std::string_view mountPoint, const auto storageIt = ifs->makeStorage(ifs->mountId); if (storageIt == ifs->storages.end()) { - LOG(ERROR) << "Can't create default storage directory"; + LOG(ERROR) << "Can't create a default storage directory"; return kInvalidStorageId; } @@ -359,9 +419,12 @@ StorageId IncrementalService::createStorage(std::string_view mountPoint, m.mutable_loader()->release_arguments(); m.mutable_loader()->release_class_name(); m.mutable_loader()->release_package_name(); - if (auto err = mIncFs->makeFile(ifs->control, constants().infoMdName, INCFS_ROOT_INODE, 0, - metadata); - err < 0) { + if (auto err = + mIncFs->makeFile(ifs->control, + path::join(ifs->root, constants().mount, + constants().infoMdName), + 0777, idFromMetadata(metadata), + {.metadata = {metadata.data(), (IncFsSize)metadata.size()}})) { LOG(ERROR) << "Saving mount metadata failed: " << -err; return kInvalidStorageId; } @@ -369,8 +432,8 @@ StorageId IncrementalService::createStorage(std::string_view mountPoint, const auto bk = (options & CreateOptions::PermanentBind) ? BindKind::Permanent : BindKind::Temporary; - if (auto err = addBindMount(*ifs, storageIt->first, std::string(storageIt->second.name), - std::move(mountNorm), bk, l); + if (auto err = addBindMount(*ifs, storageIt->first, storageIt->second.name, + std::string(storageIt->second.name), std::move(mountNorm), bk, l); err < 0) { LOG(ERROR) << "adding bind mount failed: " << -err; return kInvalidStorageId; @@ -419,8 +482,9 @@ StorageId IncrementalService::createLinkedStorage(std::string_view mountPoint, const auto bk = (options & CreateOptions::PermanentBind) ? BindKind::Permanent : BindKind::Temporary; - if (auto err = addBindMount(*ifs, storageIt->first, std::string(storageIt->second.name), - path::normalize(mountPoint), bk, l); + if (auto err = addBindMount(*ifs, storageIt->first, storageIt->second.name, + std::string(storageIt->second.name), path::normalize(mountPoint), + bk, l); err < 0) { LOG(ERROR) << "bindMount failed with error: " << err; return kInvalidStorageId; @@ -492,40 +556,36 @@ StorageId IncrementalService::openStorage(std::string_view pathInMount) { return findStorageId(path::normalize(pathInMount)); } -Inode IncrementalService::nodeFor(StorageId storage, std::string_view subpath) const { +FileId IncrementalService::nodeFor(StorageId storage, std::string_view subpath) const { const auto ifs = getIfs(storage); if (!ifs) { - return -1; + return kIncFsInvalidFileId; } std::unique_lock l(ifs->lock); auto storageIt = ifs->storages.find(storage); if (storageIt == ifs->storages.end()) { - return -1; + return kIncFsInvalidFileId; } if (subpath.empty() || subpath == "."sv) { - return storageIt->second.node; + return kIncFsInvalidFileId; } auto path = path::join(ifs->root, constants().mount, storageIt->second.name, subpath); l.unlock(); - struct stat st; - if (::stat(path.c_str(), &st)) { - return -1; - } - return st.st_ino; + return mIncFs->getFileId(ifs->control, path); } -std::pair<Inode, std::string_view> IncrementalService::parentAndNameFor( +std::pair<FileId, std::string_view> IncrementalService::parentAndNameFor( StorageId storage, std::string_view subpath) const { auto name = path::basename(subpath); if (name.empty()) { - return {-1, {}}; + return {kIncFsInvalidFileId, {}}; } auto dir = path::dirname(subpath); if (dir.empty() || dir == "/"sv) { - return {-1, {}}; + return {kIncFsInvalidFileId, {}}; } - auto inode = nodeFor(storage, dir); - return {inode, name}; + auto id = nodeFor(storage, dir); + return {id, name}; } IncrementalService::IfsMountPtr IncrementalService::getIfs(StorageId storage) const { @@ -542,8 +602,8 @@ const IncrementalService::IfsMountPtr& IncrementalService::getIfsLocked(StorageI return it->second; } -int IncrementalService::bind(StorageId storage, std::string_view sourceSubdir, - std::string_view target, BindKind kind) { +int IncrementalService::bind(StorageId storage, std::string_view source, std::string_view target, + BindKind kind) { if (!isValidMountTarget(target)) { return -EINVAL; } @@ -552,15 +612,20 @@ int IncrementalService::bind(StorageId storage, std::string_view sourceSubdir, if (!ifs) { return -EINVAL; } + auto normSource = path::normalize(source); + std::unique_lock l(ifs->lock); const auto storageInfo = ifs->storages.find(storage); if (storageInfo == ifs->storages.end()) { return -EINVAL; } - auto source = path::join(storageInfo->second.name, sourceSubdir); + if (!path::startsWith(normSource, storageInfo->second.name)) { + return -EINVAL; + } l.unlock(); std::unique_lock l2(mLock, std::defer_lock); - return addBindMount(*ifs, storage, std::move(source), path::normalize(target), kind, l2); + return addBindMount(*ifs, storage, storageInfo->second.name, std::move(normSource), + path::normalize(target), kind, l2); } int IncrementalService::unbind(StorageId storage, std::string_view target) { @@ -599,90 +664,72 @@ int IncrementalService::unbind(StorageId storage, std::string_view target) { ifs->bindPoints.erase(bindIt); l2.unlock(); if (!savedFile.empty()) { - mIncFs->unlink(ifs->control, INCFS_ROOT_INODE, savedFile); + mIncFs->unlink(ifs->control, path::join(ifs->root, constants().mount, savedFile)); } } return 0; } -Inode IncrementalService::makeFile(StorageId storageId, std::string_view pathUnderStorage, - long size, std::string_view metadata, - std::string_view signature) { - (void)signature; - auto [parentInode, name] = parentAndNameFor(storageId, pathUnderStorage); - if (parentInode < 0) { - return -EINVAL; - } - if (auto ifs = getIfs(storageId)) { - auto inode = mIncFs->makeFile(ifs->control, name, parentInode, size, metadata); - if (inode < 0) { - return inode; +int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id, + incfs::NewFileParams params) { + if (auto ifs = getIfs(storage)) { + auto err = mIncFs->makeFile(ifs->control, path, mode, id, params); + if (err) { + return err; } - auto metadataBytes = std::vector<uint8_t>(); - if (metadata.data() != nullptr && metadata.size() > 0) { - metadataBytes.insert(metadataBytes.end(), &metadata.data()[0], - &metadata.data()[metadata.size()]); + std::vector<uint8_t> metadataBytes; + if (params.metadata.data && params.metadata.size > 0) { + metadataBytes.assign(params.metadata.data, params.metadata.data + params.metadata.size); } - mIncrementalManager->newFileForDataLoader(ifs->mountId, inode, metadataBytes); - return inode; + mIncrementalManager->newFileForDataLoader(ifs->mountId, id, metadataBytes); + return 0; } return -EINVAL; } -Inode IncrementalService::makeDir(StorageId storageId, std::string_view pathUnderStorage, - std::string_view metadata) { - auto [parentInode, name] = parentAndNameFor(storageId, pathUnderStorage); - if (parentInode < 0) { - return -EINVAL; - } +int IncrementalService::makeDir(StorageId storageId, std::string_view path, int mode) { if (auto ifs = getIfs(storageId)) { - return mIncFs->makeDir(ifs->control, name, parentInode, metadata); + return mIncFs->makeDir(ifs->control, path, mode); } return -EINVAL; } -Inode IncrementalService::makeDirs(StorageId storageId, std::string_view pathUnderStorage, - std::string_view metadata) { +int IncrementalService::makeDirs(StorageId storageId, std::string_view path, int mode) { const auto ifs = getIfs(storageId); if (!ifs) { return -EINVAL; } - std::string_view parentDir(pathUnderStorage); - auto p = parentAndNameFor(storageId, pathUnderStorage); - std::stack<std::string> pathsToCreate; - while (p.first < 0) { - parentDir = path::dirname(parentDir); - pathsToCreate.emplace(parentDir); - p = parentAndNameFor(storageId, parentDir); - } - Inode inode; - while (!pathsToCreate.empty()) { - p = parentAndNameFor(storageId, pathsToCreate.top()); - pathsToCreate.pop(); - inode = mIncFs->makeDir(ifs->control, p.second, p.first, metadata); - if (inode < 0) { - return inode; - } + + auto err = mIncFs->makeDir(ifs->control, path, mode); + if (err == -EEXIST) { + return 0; + } else if (err != -ENOENT) { + return err; + } + if (auto err = makeDirs(storageId, path::dirname(path), mode)) { + return err; } - return mIncFs->makeDir(ifs->control, path::basename(pathUnderStorage), inode, metadata); + return mIncFs->makeDir(ifs->control, path, mode); } -int IncrementalService::link(StorageId storage, Inode item, Inode newParent, - std::string_view newName) { - if (auto ifs = getIfs(storage)) { - return mIncFs->link(ifs->control, item, newParent, newName); +int IncrementalService::link(StorageId sourceStorageId, std::string_view oldPath, + StorageId destStorageId, std::string_view newPath) { + if (auto ifsSrc = getIfs(sourceStorageId), ifsDest = getIfs(destStorageId); + ifsSrc && ifsSrc == ifsDest) { + return mIncFs->link(ifsSrc->control, oldPath, newPath); } return -EINVAL; } -int IncrementalService::unlink(StorageId storage, Inode parent, std::string_view name) { +int IncrementalService::unlink(StorageId storage, std::string_view path) { if (auto ifs = getIfs(storage)) { - return mIncFs->unlink(ifs->control, parent, name); + return mIncFs->unlink(ifs->control, path); } return -EINVAL; } -int IncrementalService::addBindMount(IncFsMount& ifs, StorageId storage, std::string&& sourceSubdir, +int IncrementalService::addBindMount(IncFsMount& ifs, StorageId storage, + std::string_view storageRoot, std::string&& source, std::string&& target, BindKind kind, std::unique_lock<std::mutex>& mainLock) { if (!isValidMountTarget(target)) { @@ -694,30 +741,30 @@ int IncrementalService::addBindMount(IncFsMount& ifs, StorageId storage, std::st metadata::BindPoint bp; bp.set_storage_id(storage); bp.set_allocated_dest_path(&target); - bp.set_allocated_source_subdir(&sourceSubdir); + bp.set_source_subdir(std::string(path::relativize(storageRoot, source))); const auto metadata = bp.SerializeAsString(); - bp.release_source_subdir(); bp.release_dest_path(); mdFileName = makeBindMdName(); - auto node = mIncFs->makeFile(ifs.control, mdFileName, INCFS_ROOT_INODE, 0, metadata); - if (node < 0) { + auto node = + mIncFs->makeFile(ifs.control, path::join(ifs.root, constants().mount, mdFileName), + 0444, idFromMetadata(metadata), + {.metadata = {metadata.data(), (IncFsSize)metadata.size()}}); + if (node) { return int(node); } } - return addBindMountWithMd(ifs, storage, std::move(mdFileName), std::move(sourceSubdir), + return addBindMountWithMd(ifs, storage, std::move(mdFileName), std::move(source), std::move(target), kind, mainLock); } int IncrementalService::addBindMountWithMd(IncrementalService::IncFsMount& ifs, StorageId storage, - std::string&& metadataName, std::string&& sourceSubdir, + std::string&& metadataName, std::string&& source, std::string&& target, BindKind kind, std::unique_lock<std::mutex>& mainLock) { - LOG(INFO) << "Adding bind mount: " << sourceSubdir << " -> " << target; { - auto path = path::join(ifs.root, constants().mount, sourceSubdir); std::lock_guard l(mMountOperationLock); - const auto status = mVold->bindMount(path, target); + const auto status = mVold->bindMount(source, target); if (!status.isOk()) { LOG(ERROR) << "Calling Vold::bindMount() failed: " << status.toString8(); return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC @@ -736,12 +783,12 @@ int IncrementalService::addBindMountWithMd(IncrementalService::IncFsMount& ifs, const auto [it, _] = ifs.bindPoints.insert_or_assign(target, IncFsMount::Bind{storage, std::move(metadataName), - std::move(sourceSubdir), kind}); + std::move(source), kind}); mBindsByPath[std::move(target)] = it; return 0; } -RawMetadata IncrementalService::getMetadata(StorageId storage, Inode node) const { +RawMetadata IncrementalService::getMetadata(StorageId storage, FileId node) const { const auto ifs = getIfs(storage); if (!ifs) { return {}; @@ -831,21 +878,18 @@ bool IncrementalService::mountExistingImage(std::string_view root, std::string_v LOG(INFO) << "Trying to mount: " << key; auto mountTarget = path::join(root, constants().mount); - const auto image = path::join(root, constants().backing, constants().image); + const auto backing = path::join(root, constants().backing); IncFsMount::Control control; IncrementalFileSystemControlParcel controlParcel; - auto status = mVold->mountIncFs(image, mountTarget, 0, &controlParcel); + auto status = mVold->mountIncFs(backing, mountTarget, 0, &controlParcel); if (!status.isOk()) { LOG(ERROR) << "Vold::mountIncFs() failed: " << status.toString8(); return false; } - if (controlParcel.cmd) { - control.cmdFd = controlParcel.cmd->release(); - } - if (controlParcel.log) { - control.logFd = controlParcel.log->release(); - } + control.cmd = controlParcel.cmd.release().release(); + control.pendingReads = controlParcel.pendingReads.release().release(); + control.logs = controlParcel.log.release().release(); auto ifs = std::make_shared<IncFsMount>(std::string(root), -1, std::move(control), *this); @@ -860,8 +904,7 @@ bool IncrementalService::mountExistingImage(std::string_view root, std::string_v mNextId = std::max(mNextId, ifs->mountId + 1); std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints; - auto d = std::unique_ptr<DIR, decltype(&::closedir)>(::opendir(path::c_str(mountTarget)), - ::closedir); + auto d = openDir(path::c_str(mountTarget)); while (auto e = ::readdir(d.get())) { if (e->d_type == DT_REG) { auto name = std::string_view(e->d_name); @@ -874,7 +917,7 @@ bool IncrementalService::mountExistingImage(std::string_view root, std::string_v if (bindPoints.back().second.dest_path().empty() || bindPoints.back().second.source_subdir().empty()) { bindPoints.pop_back(); - mIncFs->unlink(ifs->control, INCFS_ROOT_INODE, name); + mIncFs->unlink(ifs->control, path::join(ifs->root, constants().mount, name)); } } } else if (e->d_type == DT_DIR) { @@ -891,9 +934,7 @@ bool IncrementalService::mountExistingImage(std::string_view root, std::string_v << " for mount " << root; continue; } - ifs->storages.insert_or_assign(md.id(), - IncFsMount::Storage{std::string(name), - Inode(e->d_ino)}); + ifs->storages.insert_or_assign(md.id(), IncFsMount::Storage{std::string(name)}); mNextId = std::max(mNextId, md.id() + 1); } } @@ -973,10 +1014,10 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, } FileSystemControlParcel fsControlParcel; fsControlParcel.incremental = std::make_unique<IncrementalFileSystemControlParcel>(); - fsControlParcel.incremental->cmd = - std::make_unique<os::ParcelFileDescriptor>(base::unique_fd(::dup(ifs.control.cmdFd))); - fsControlParcel.incremental->log = - std::make_unique<os::ParcelFileDescriptor>(base::unique_fd(::dup(ifs.control.logFd))); + fsControlParcel.incremental->cmd.reset(base::unique_fd(::dup(ifs.control.cmd))); + fsControlParcel.incremental->pendingReads.reset( + base::unique_fd(::dup(ifs.control.pendingReads))); + fsControlParcel.incremental->log.reset(base::unique_fd(::dup(ifs.control.logs))); sp<IncrementalDataLoaderListener> listener = new IncrementalDataLoaderListener(*this); bool created = false; auto status = mIncrementalManager->prepareDataLoader(ifs.mountId, fsControlParcel, *dlp, diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index a03ffa00d035..ca5e4dbd9231 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -52,16 +52,16 @@ namespace android::incremental { using MountId = int; using StorageId = int; -using Inode = incfs::Inode; +using FileId = incfs::FileId; using BlockIndex = incfs::BlockIndex; using RawMetadata = incfs::RawMetadata; using Clock = std::chrono::steady_clock; using TimePoint = std::chrono::time_point<Clock>; using Seconds = std::chrono::seconds; -class IncrementalService { +class IncrementalService final { public: - explicit IncrementalService(const ServiceManagerWrapper& sm, std::string_view rootDir); + explicit IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -85,6 +85,11 @@ public: Permanent = 1, }; + static FileId idFromMetadata(std::span<const uint8_t> metadata); + static inline FileId idFromMetadata(std::span<const char> metadata) { + return idFromMetadata({(const uint8_t*)metadata.data(), metadata.size()}); + } + std::optional<std::future<void>> onSystemReady(); StorageId createStorage(std::string_view mountPoint, @@ -92,30 +97,31 @@ public: CreateOptions options = CreateOptions::Default); StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage, CreateOptions options = CreateOptions::Default); - StorageId openStorage(std::string_view pathInMount); + StorageId openStorage(std::string_view path); - Inode nodeFor(StorageId storage, std::string_view subpath) const; - std::pair<Inode, std::string_view> parentAndNameFor(StorageId storage, - std::string_view subpath) const; + FileId nodeFor(StorageId storage, std::string_view path) const; + std::pair<FileId, std::string_view> parentAndNameFor(StorageId storage, + std::string_view path) const; - int bind(StorageId storage, std::string_view subdir, std::string_view target, BindKind kind); + int bind(StorageId storage, std::string_view source, std::string_view target, BindKind kind); int unbind(StorageId storage, std::string_view target); void deleteStorage(StorageId storage); - Inode makeFile(StorageId storage, std::string_view name, long size, std::string_view metadata, - std::string_view signature); - Inode makeDir(StorageId storage, std::string_view name, std::string_view metadata = {}); - Inode makeDirs(StorageId storage, std::string_view name, std::string_view metadata = {}); + int makeFile(StorageId storage, std::string_view path, int mode, FileId id, + incfs::NewFileParams params); + int makeDir(StorageId storage, std::string_view path, int mode = 0555); + int makeDirs(StorageId storage, std::string_view path, int mode = 0555); - int link(StorageId storage, Inode item, Inode newParent, std::string_view newName); - int unlink(StorageId storage, Inode parent, std::string_view name); + int link(StorageId sourceStorageId, std::string_view oldPath, StorageId destStorageId, + std::string_view newPath); + int unlink(StorageId storage, std::string_view path); - bool isRangeLoaded(StorageId storage, Inode file, std::pair<BlockIndex, BlockIndex> range) { + bool isRangeLoaded(StorageId storage, FileId file, std::pair<BlockIndex, BlockIndex> range) { return false; } - RawMetadata getMetadata(StorageId storage, Inode node) const; - std::string getSigngatureData(StorageId storage, Inode node) const { return {}; } + RawMetadata getMetadata(StorageId storage, FileId node) const; + std::string getSignatureData(StorageId storage, FileId node) const; std::vector<std::string> listFiles(StorageId storage) const; bool startLoading(StorageId storage) const; @@ -142,19 +148,9 @@ private: struct Storage { std::string name; - Inode node; }; - struct Control { - operator IncFsControl() const { return {cmdFd, logFd}; } - void reset() { - cmdFd.reset(); - logFd.reset(); - } - - base::unique_fd cmdFd; - base::unique_fd logFd; - }; + using Control = incfs::UniqueControl; using BindMap = std::map<std::string, Bind>; using StorageMap = std::unordered_map<StorageId, Storage>; @@ -196,11 +192,12 @@ private: IfsMountPtr getIfs(StorageId storage) const; const IfsMountPtr& getIfsLocked(StorageId storage) const; - int addBindMount(IncFsMount& ifs, StorageId storage, std::string&& sourceSubdir, - std::string&& target, BindKind kind, std::unique_lock<std::mutex>& mainLock); + int addBindMount(IncFsMount& ifs, StorageId storage, std::string_view storageRoot, + std::string&& source, std::string&& target, BindKind kind, + std::unique_lock<std::mutex>& mainLock); int addBindMountWithMd(IncFsMount& ifs, StorageId storage, std::string&& metadataName, - std::string&& sourceSubdir, std::string&& target, BindKind kind, + std::string&& source, std::string&& target, BindKind kind, std::unique_lock<std::mutex>& mainLock); bool prepareDataLoader(IncFsMount& ifs, DataLoaderParamsParcel* params); @@ -212,10 +209,9 @@ private: MountMap::iterator getStorageSlotLocked(); // Member variables - // These are shared pointers for the sake of unit testing - std::shared_ptr<VoldServiceWrapper> mVold; - std::shared_ptr<IncrementalManagerWrapper> mIncrementalManager; - std::shared_ptr<IncFsWrapper> mIncFs; + std::unique_ptr<VoldServiceWrapper> mVold; + std::unique_ptr<IncrementalManagerWrapper> mIncrementalManager; + std::unique_ptr<IncFsWrapper> mIncFs; const std::string mIncrementalDir; mutable std::mutex mLock; diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index a79b26ae4fb3..5d978a1cf741 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -16,14 +16,8 @@ #include "ServiceWrappers.h" -#include <android-base/strings.h> -#include <android-base/unique_fd.h> -#include <binder/IServiceManager.h> #include <utils/String16.h> -#include <string> -#include <string_view> - using namespace std::literals; namespace android::os::incremental { @@ -31,37 +25,38 @@ namespace android::os::incremental { static constexpr auto kVoldServiceName = "vold"sv; static constexpr auto kIncrementalManagerName = "incremental"sv; -RealServiceManager::RealServiceManager(const sp<IServiceManager>& serviceManager) - : mServiceManager(serviceManager) {} +RealServiceManager::RealServiceManager(sp<IServiceManager> serviceManager) + : mServiceManager(std::move(serviceManager)) {} template <class INTERFACE> sp<INTERFACE> RealServiceManager::getRealService(std::string_view serviceName) const { - sp<IBinder> binder = mServiceManager->getService(String16(serviceName.data())); - if (binder == 0) { - return 0; + sp<IBinder> binder = + mServiceManager->getService(String16(serviceName.data(), serviceName.size())); + if (!binder) { + return nullptr; } return interface_cast<INTERFACE>(binder); } -std::shared_ptr<VoldServiceWrapper> RealServiceManager::getVoldService() const { +std::unique_ptr<VoldServiceWrapper> RealServiceManager::getVoldService() { sp<os::IVold> vold = RealServiceManager::getRealService<os::IVold>(kVoldServiceName); if (vold != 0) { - return std::make_shared<RealVoldService>(vold); + return std::make_unique<RealVoldService>(vold); } return nullptr; } -std::shared_ptr<IncrementalManagerWrapper> RealServiceManager::getIncrementalManager() const { +std::unique_ptr<IncrementalManagerWrapper> RealServiceManager::getIncrementalManager() { sp<IIncrementalManager> manager = RealServiceManager::getRealService<IIncrementalManager>(kIncrementalManagerName); - if (manager != 0) { - return std::make_shared<RealIncrementalManager>(manager); + if (manager) { + return std::make_unique<RealIncrementalManager>(manager); } return nullptr; } -std::shared_ptr<IncFsWrapper> RealServiceManager::getIncFs() const { - return std::make_shared<RealIncFs>(); +std::unique_ptr<IncFsWrapper> RealServiceManager::getIncFs() { + return std::make_unique<RealIncFs>(); } } // namespace android::os::incremental diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index 570458283ac9..ae3739dba2f0 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -26,6 +26,7 @@ #include <binder/IServiceManager.h> #include <incfs.h> +#include <memory> #include <string> #include <string_view> @@ -36,10 +37,12 @@ namespace android::os::incremental { // --- Wrapper interfaces --- +using MountId = int32_t; + class VoldServiceWrapper { public: - virtual ~VoldServiceWrapper(){}; - virtual binder::Status mountIncFs(const std::string& imagePath, const std::string& targetDir, + virtual ~VoldServiceWrapper() = default; + virtual binder::Status mountIncFs(const std::string& backingPath, const std::string& targetDir, int32_t flags, IncrementalFileSystemControlParcel* _aidl_return) const = 0; virtual binder::Status unmountIncFs(const std::string& dir) const = 0; @@ -49,52 +52,52 @@ public: class IncrementalManagerWrapper { public: - virtual ~IncrementalManagerWrapper() {} - virtual binder::Status prepareDataLoader( - int32_t mountId, const FileSystemControlParcel& control, - const DataLoaderParamsParcel& params, - const sp<IDataLoaderStatusListener>& listener, - bool* _aidl_return) const = 0; - virtual binder::Status startDataLoader(int32_t mountId, bool* _aidl_return) const = 0; - virtual binder::Status destroyDataLoader(int32_t mountId) const = 0; - virtual binder::Status newFileForDataLoader(int32_t mountId, int64_t inode, - const ::std::vector<uint8_t>& metadata) const = 0; - virtual binder::Status showHealthBlockedUI(int32_t mountId) const = 0; + virtual ~IncrementalManagerWrapper() = default; + virtual binder::Status prepareDataLoader(MountId mountId, + const FileSystemControlParcel& control, + const DataLoaderParamsParcel& params, + const sp<IDataLoaderStatusListener>& listener, + bool* _aidl_return) const = 0; + virtual binder::Status startDataLoader(MountId mountId, bool* _aidl_return) const = 0; + virtual binder::Status destroyDataLoader(MountId mountId) const = 0; + virtual binder::Status newFileForDataLoader(MountId mountId, FileId fileid, + const std::vector<uint8_t>& metadata) const = 0; + virtual binder::Status showHealthBlockedUI(MountId mountId) const = 0; }; class IncFsWrapper { public: - virtual ~IncFsWrapper() {} - virtual Inode makeFile(Control control, std::string_view name, Inode parent, Size size, - std::string_view metadata) const = 0; - virtual Inode makeDir(Control control, std::string_view name, Inode parent, - std::string_view metadata, int mode = 0555) const = 0; - virtual RawMetadata getMetadata(Control control, Inode inode) const = 0; - virtual ErrorCode link(Control control, Inode item, Inode targetParent, - std::string_view name) const = 0; - virtual ErrorCode unlink(Control control, Inode parent, std::string_view name) const = 0; - virtual ErrorCode writeBlocks(Control control, const incfs_new_data_block blocks[], - int blocksCount) const = 0; + virtual ~IncFsWrapper() = default; + virtual ErrorCode makeFile(Control control, std::string_view path, int mode, FileId id, + NewFileParams params) const = 0; + virtual ErrorCode makeDir(Control control, std::string_view path, int mode = 0555) const = 0; + virtual RawMetadata getMetadata(Control control, FileId fileid) const = 0; + virtual RawMetadata getMetadata(Control control, std::string_view path) const = 0; + virtual FileId getFileId(Control control, std::string_view path) const = 0; + virtual ErrorCode link(Control control, std::string_view from, std::string_view to) const = 0; + virtual ErrorCode unlink(Control control, std::string_view path) const = 0; + virtual base::unique_fd openWrite(Control control, FileId id) const = 0; + virtual ErrorCode writeBlocks(std::span<const DataBlock> blocks) const = 0; }; class ServiceManagerWrapper { public: - virtual ~ServiceManagerWrapper() {} - virtual std::shared_ptr<VoldServiceWrapper> getVoldService() const = 0; - virtual std::shared_ptr<IncrementalManagerWrapper> getIncrementalManager() const = 0; - virtual std::shared_ptr<IncFsWrapper> getIncFs() const = 0; + virtual ~ServiceManagerWrapper() = default; + virtual std::unique_ptr<VoldServiceWrapper> getVoldService() = 0; + virtual std::unique_ptr<IncrementalManagerWrapper> getIncrementalManager() = 0; + virtual std::unique_ptr<IncFsWrapper> getIncFs() = 0; }; // --- Real stuff --- class RealVoldService : public VoldServiceWrapper { public: - RealVoldService(const sp<os::IVold> vold) : mInterface(vold) {} + RealVoldService(const sp<os::IVold> vold) : mInterface(std::move(vold)) {} ~RealVoldService() = default; - binder::Status mountIncFs(const std::string& imagePath, const std::string& targetDir, + binder::Status mountIncFs(const std::string& backingPath, const std::string& targetDir, int32_t flags, IncrementalFileSystemControlParcel* _aidl_return) const override { - return mInterface->mountIncFs(imagePath, targetDir, flags, _aidl_return); + return mInterface->mountIncFs(backingPath, targetDir, flags, _aidl_return); } binder::Status unmountIncFs(const std::string& dir) const override { return mInterface->unmountIncFs(dir); @@ -113,24 +116,26 @@ public: RealIncrementalManager(const sp<os::incremental::IIncrementalManager> manager) : mInterface(manager) {} ~RealIncrementalManager() = default; - binder::Status prepareDataLoader( - int32_t mountId, const FileSystemControlParcel& control, - const DataLoaderParamsParcel& params, - const sp<IDataLoaderStatusListener>& listener, - bool* _aidl_return) const override { + binder::Status prepareDataLoader(MountId mountId, const FileSystemControlParcel& control, + const DataLoaderParamsParcel& params, + const sp<IDataLoaderStatusListener>& listener, + bool* _aidl_return) const override { return mInterface->prepareDataLoader(mountId, control, params, listener, _aidl_return); } - binder::Status startDataLoader(int32_t mountId, bool* _aidl_return) const override { + binder::Status startDataLoader(MountId mountId, bool* _aidl_return) const override { return mInterface->startDataLoader(mountId, _aidl_return); } - binder::Status destroyDataLoader(int32_t mountId) const override { + binder::Status destroyDataLoader(MountId mountId) const override { return mInterface->destroyDataLoader(mountId); } - binder::Status newFileForDataLoader(int32_t mountId, int64_t inode, - const ::std::vector<uint8_t>& metadata) const override { - return mInterface->newFileForDataLoader(mountId, inode, metadata); + binder::Status newFileForDataLoader(MountId mountId, FileId fileid, + const std::vector<uint8_t>& metadata) const override { + return mInterface->newFileForDataLoader(mountId, + {(const uint8_t*)fileid.data, + (const uint8_t*)fileid.data + sizeof(fileid.data)}, + metadata); } - binder::Status showHealthBlockedUI(int32_t mountId) const override { + binder::Status showHealthBlockedUI(MountId mountId) const override { return mInterface->showHealthBlockedUI(mountId); } @@ -140,11 +145,11 @@ private: class RealServiceManager : public ServiceManagerWrapper { public: - RealServiceManager(const sp<IServiceManager>& serviceManager); + RealServiceManager(sp<IServiceManager> serviceManager); ~RealServiceManager() = default; - std::shared_ptr<VoldServiceWrapper> getVoldService() const override; - std::shared_ptr<IncrementalManagerWrapper> getIncrementalManager() const override; - std::shared_ptr<IncFsWrapper> getIncFs() const override; + std::unique_ptr<VoldServiceWrapper> getVoldService() override; + std::unique_ptr<IncrementalManagerWrapper> getIncrementalManager() override; + std::unique_ptr<IncFsWrapper> getIncFs() override; private: template <class INTERFACE> @@ -156,27 +161,33 @@ class RealIncFs : public IncFsWrapper { public: RealIncFs() = default; ~RealIncFs() = default; - Inode makeFile(Control control, std::string_view name, Inode parent, Size size, - std::string_view metadata) const override { - return incfs::makeFile(control, name, parent, size, metadata); + ErrorCode makeFile(Control control, std::string_view path, int mode, FileId id, + NewFileParams params) const override { + return incfs::makeFile(control, path, mode, id, params); + } + ErrorCode makeDir(Control control, std::string_view path, int mode) const override { + return incfs::makeDir(control, path, mode); + } + RawMetadata getMetadata(Control control, FileId fileid) const override { + return incfs::getMetadata(control, fileid); + } + RawMetadata getMetadata(Control control, std::string_view path) const override { + return incfs::getMetadata(control, path); } - Inode makeDir(Control control, std::string_view name, Inode parent, std::string_view metadata, - int mode) const override { - return incfs::makeDir(control, name, parent, metadata, mode); + FileId getFileId(Control control, std::string_view path) const override { + return incfs::getFileId(control, path); } - RawMetadata getMetadata(Control control, Inode inode) const override { - return incfs::getMetadata(control, inode); + ErrorCode link(Control control, std::string_view from, std::string_view to) const override { + return incfs::link(control, from, to); } - ErrorCode link(Control control, Inode item, Inode targetParent, - std::string_view name) const override { - return incfs::link(control, item, targetParent, name); + ErrorCode unlink(Control control, std::string_view path) const override { + return incfs::unlink(control, path); } - ErrorCode unlink(Control control, Inode parent, std::string_view name) const override { - return incfs::unlink(control, parent, name); + base::unique_fd openWrite(Control control, FileId id) const override { + return base::unique_fd{incfs::openWrite(control, id)}; } - ErrorCode writeBlocks(Control control, const incfs_new_data_block blocks[], - int blocksCount) const override { - return incfs::writeBlocks(control, blocks, blocksCount); + ErrorCode writeBlocks(std::span<const DataBlock> blocks) const override { + return incfs::writeBlocks(blocks); } }; diff --git a/services/incremental/path.cpp b/services/incremental/path.cpp index c529d61abd15..0d86f2a984d6 100644 --- a/services/incremental/path.cpp +++ b/services/incremental/path.cpp @@ -44,16 +44,45 @@ bool PathLess::operator()(std::string_view l, std::string_view r) const { PathCharsLess()); } +static void preparePathComponent(std::string_view path, bool trimFront) { + if (trimFront) { + while (!path.empty() && path.front() == '/') { + path.remove_prefix(1); + } + } + while (!path.empty() && path.back() == '/') { + path.remove_suffix(1); + } +} + void details::append_next_path(std::string& target, std::string_view path) { + preparePathComponent(path, true); if (path.empty()) { return; } - if (!target.empty()) { + if (!target.empty() && !target.ends_with('/')) { target.push_back('/'); } target += path; } +std::string_view relativize(std::string_view parent, std::string_view nested) { + if (!nested.starts_with(parent)) { + return nested; + } + if (nested.size() == parent.size()) { + return {}; + } + if (nested[parent.size()] != '/') { + return nested; + } + auto relative = nested.substr(parent.size()); + while (relative.front() == '/') { + relative.remove_prefix(1); + } + return relative; +} + bool isAbsolute(std::string_view path) { return !path.empty() && path[0] == '/'; } diff --git a/services/incremental/path.h b/services/incremental/path.h index a1f4b8ec3093..3e5fd21b6535 100644 --- a/services/incremental/path.h +++ b/services/incremental/path.h @@ -67,6 +67,20 @@ inline details::CStrWrapper c_str(std::string_view sv) { return {sv}; } +std::string_view relativize(std::string_view parent, std::string_view nested); +inline std::string_view relativize(const char* parent, const char* nested) { + return relativize(std::string_view(parent), std::string_view(nested)); +} +inline std::string_view relativize(std::string_view parent, const char* nested) { + return relativize(parent, std::string_view(nested)); +} +inline std::string_view relativize(const char* parent, std::string_view nested) { + return relativize(std::string_view(parent), nested); +} + +std::string_view relativize(std::string&& parent, std::string_view nested) = delete; +std::string_view relativize(std::string_view parent, std::string&& nested) = delete; + bool isAbsolute(std::string_view path); std::string normalize(std::string_view path); std::string_view dirname(std::string_view path); diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index ca1e1a972047..28268181f173 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -46,7 +46,7 @@ namespace android::os::incremental { class MockVoldService : public VoldServiceWrapper { public: MOCK_CONST_METHOD4(mountIncFs, - binder::Status(const std::string& imagePath, const std::string& targetDir, + binder::Status(const std::string& backingPath, const std::string& targetDir, int32_t flags, IncrementalFileSystemControlParcel* _aidl_return)); MOCK_CONST_METHOD1(unmountIncFs, binder::Status(const std::string& dir)); @@ -77,22 +77,20 @@ public: binder::Status getInvalidControlParcel(const std::string& imagePath, const std::string& targetDir, int32_t flags, IncrementalFileSystemControlParcel* _aidl_return) { - _aidl_return->cmd = nullptr; - _aidl_return->log = nullptr; + _aidl_return = {}; return binder::Status::ok(); } binder::Status incFsSuccess(const std::string& imagePath, const std::string& targetDir, int32_t flags, IncrementalFileSystemControlParcel* _aidl_return) { - _aidl_return->cmd = std::make_unique<os::ParcelFileDescriptor>(std::move(cmdFd)); - _aidl_return->log = std::make_unique<os::ParcelFileDescriptor>(std::move(logFd)); + _aidl_return->pendingReads.reset(base::unique_fd(dup(STDIN_FILENO))); + _aidl_return->cmd.reset(base::unique_fd(dup(STDIN_FILENO))); + _aidl_return->log.reset(base::unique_fd(dup(STDIN_FILENO))); return binder::Status::ok(); } private: TemporaryFile cmdFile; TemporaryFile logFile; - base::unique_fd cmdFd; - base::unique_fd logFd; }; class MockIncrementalManager : public IncrementalManagerWrapper { @@ -105,7 +103,7 @@ public: MOCK_CONST_METHOD2(startDataLoader, binder::Status(int32_t mountId, bool* _aidl_return)); MOCK_CONST_METHOD1(destroyDataLoader, binder::Status(int32_t mountId)); MOCK_CONST_METHOD3(newFileForDataLoader, - binder::Status(int32_t mountId, int64_t inode, + binder::Status(int32_t mountId, FileId fileId, const ::std::vector<uint8_t>& metadata)); MOCK_CONST_METHOD1(showHealthBlockedUI, binder::Status(int32_t mountId)); @@ -152,23 +150,21 @@ private: class MockIncFs : public IncFsWrapper { public: MOCK_CONST_METHOD5(makeFile, - Inode(Control control, std::string_view name, Inode parent, Size size, - std::string_view metadata)); - MOCK_CONST_METHOD5(makeDir, - Inode(Control control, std::string_view name, Inode parent, - std::string_view metadata, int mode)); - MOCK_CONST_METHOD2(getMetadata, RawMetadata(Control control, Inode inode)); - MOCK_CONST_METHOD4(link, - ErrorCode(Control control, Inode item, Inode targetParent, - std::string_view name)); - MOCK_CONST_METHOD3(unlink, ErrorCode(Control control, Inode parent, std::string_view name)); - MOCK_CONST_METHOD3(writeBlocks, - ErrorCode(Control control, const incfs_new_data_block blocks[], - int blocksCount)); + ErrorCode(Control control, std::string_view path, int mode, FileId id, + NewFileParams params)); + MOCK_CONST_METHOD3(makeDir, ErrorCode(Control control, std::string_view path, int mode)); + MOCK_CONST_METHOD2(getMetadata, RawMetadata(Control control, FileId fileid)); + MOCK_CONST_METHOD2(getMetadata, RawMetadata(Control control, std::string_view path)); + MOCK_CONST_METHOD2(getFileId, FileId(Control control, std::string_view path)); + MOCK_CONST_METHOD3(link, + ErrorCode(Control control, std::string_view from, std::string_view to)); + MOCK_CONST_METHOD2(unlink, ErrorCode(Control control, std::string_view path)); + MOCK_CONST_METHOD2(openWrite, base::unique_fd(Control control, FileId id)); + MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks)); void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); } void makeFileSuccess() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(0)); } - RawMetadata getMountInfoMetadata(Control control, Inode inode) { + RawMetadata getMountInfoMetadata(Control control, std::string_view path) { metadata::Mount m; m.mutable_storage()->set_id(100); m.mutable_loader()->set_package_name("com.test"); @@ -176,15 +172,15 @@ public: const auto metadata = m.SerializeAsString(); m.mutable_loader()->release_arguments(); m.mutable_loader()->release_package_name(); - return std::vector<char>(metadata.begin(), metadata.end()); + return {metadata.begin(), metadata.end()}; } - RawMetadata getStorageMetadata(Control control, Inode inode) { + RawMetadata getStorageMetadata(Control control, std::string_view path) { metadata::Storage st; st.set_id(100); auto metadata = st.SerializeAsString(); - return std::vector<char>(metadata.begin(), metadata.end()); + return {metadata.begin(), metadata.end()}; } - RawMetadata getBindPointMetadata(Control control, Inode inode) { + RawMetadata getBindPointMetadata(Control control, std::string_view path) { metadata::BindPoint bp; std::string destPath = "dest"; std::string srcPath = "src"; @@ -200,40 +196,41 @@ public: class MockServiceManager : public ServiceManagerWrapper { public: - MockServiceManager(std::shared_ptr<MockVoldService> vold, - std::shared_ptr<MockIncrementalManager> manager, - std::shared_ptr<MockIncFs> incfs) - : mVold(vold), mIncrementalManager(manager), mIncFs(incfs) {} - std::shared_ptr<VoldServiceWrapper> getVoldService() const override { return mVold; } - std::shared_ptr<IncrementalManagerWrapper> getIncrementalManager() const override { - return mIncrementalManager; + MockServiceManager(std::unique_ptr<MockVoldService> vold, + std::unique_ptr<MockIncrementalManager> manager, + std::unique_ptr<MockIncFs> incfs) + : mVold(std::move(vold)), + mIncrementalManager(std::move(manager)), + mIncFs(std::move(incfs)) {} + std::unique_ptr<VoldServiceWrapper> getVoldService() final { return std::move(mVold); } + std::unique_ptr<IncrementalManagerWrapper> getIncrementalManager() final { + return std::move(mIncrementalManager); } - std::shared_ptr<IncFsWrapper> getIncFs() const override { return mIncFs; } + std::unique_ptr<IncFsWrapper> getIncFs() final { return std::move(mIncFs); } private: - std::shared_ptr<MockVoldService> mVold; - std::shared_ptr<MockIncrementalManager> mIncrementalManager; - std::shared_ptr<MockIncFs> mIncFs; + std::unique_ptr<MockVoldService> mVold; + std::unique_ptr<MockIncrementalManager> mIncrementalManager; + std::unique_ptr<MockIncFs> mIncFs; }; // --- IncrementalServiceTest --- -static Inode inode(std::string_view path) { - struct stat st; - if (::stat(path::c_str(path), &st)) { - return -1; - } - return st.st_ino; -} - class IncrementalServiceTest : public testing::Test { public: void SetUp() override { - mVold = std::make_shared<NiceMock<MockVoldService>>(); - mIncrementalManager = std::make_shared<NiceMock<MockIncrementalManager>>(); - mIncFs = std::make_shared<NiceMock<MockIncFs>>(); - MockServiceManager serviceManager = MockServiceManager(mVold, mIncrementalManager, mIncFs); - mIncrementalService = std::make_unique<IncrementalService>(serviceManager, mRootDir.path); + auto vold = std::make_unique<NiceMock<MockVoldService>>(); + mVold = vold.get(); + auto incrementalManager = std::make_unique<NiceMock<MockIncrementalManager>>(); + mIncrementalManager = incrementalManager.get(); + auto incFs = std::make_unique<NiceMock<MockIncFs>>(); + mIncFs = incFs.get(); + mIncrementalService = + std::make_unique<IncrementalService>(MockServiceManager(std::move(vold), + std::move( + incrementalManager), + std::move(incFs)), + mRootDir.path); mDataLoaderParcel.packageName = "com.test"; mDataLoaderParcel.arguments = "uri"; mIncrementalService->onSystemReady(); @@ -252,20 +249,18 @@ public: const auto mountPointsFile = rootDir + "/dir1/mount/.mountpoint.abcd"; ASSERT_TRUE(base::WriteStringToFile("info", mountInfoFile)); ASSERT_TRUE(base::WriteStringToFile("mounts", mountPointsFile)); - ASSERT_GE(inode(mountInfoFile), 0); - ASSERT_GE(inode(mountPointsFile), 0); - ON_CALL(*mIncFs, getMetadata(_, inode(mountInfoFile))) - .WillByDefault(Invoke(mIncFs.get(), &MockIncFs::getMountInfoMetadata)); - ON_CALL(*mIncFs, getMetadata(_, inode(mountPointsFile))) - .WillByDefault(Invoke(mIncFs.get(), &MockIncFs::getBindPointMetadata)); - ON_CALL(*mIncFs, getMetadata(_, inode(rootDir + "/dir1/mount/st0"))) - .WillByDefault(Invoke(mIncFs.get(), &MockIncFs::getStorageMetadata)); + ON_CALL(*mIncFs, getMetadata(_, std::string_view(mountInfoFile))) + .WillByDefault(Invoke(mIncFs, &MockIncFs::getMountInfoMetadata)); + ON_CALL(*mIncFs, getMetadata(_, std::string_view(mountPointsFile))) + .WillByDefault(Invoke(mIncFs, &MockIncFs::getBindPointMetadata)); + ON_CALL(*mIncFs, getMetadata(_, std::string_view(rootDir + "/dir1/mount/st0"))) + .WillByDefault(Invoke(mIncFs, &MockIncFs::getStorageMetadata)); } protected: - std::shared_ptr<NiceMock<MockVoldService>> mVold; - std::shared_ptr<NiceMock<MockIncFs>> mIncFs; - std::shared_ptr<NiceMock<MockIncrementalManager>> mIncrementalManager; + NiceMock<MockVoldService>* mVold; + NiceMock<MockIncFs>* mIncFs; + NiceMock<MockIncrementalManager>* mIncrementalManager; std::unique_ptr<IncrementalService> mIncrementalService; TemporaryDir mRootDir; DataLoaderParamsParcel mDataLoaderParcel; @@ -412,12 +407,12 @@ TEST_F(IncrementalServiceTest, testMakeDirectory) { mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew); std::string_view dir_path("test"); - EXPECT_CALL(*mIncFs, makeDir(_, dir_path, _, _, _)); - int fileIno = mIncrementalService->makeDir(storageId, dir_path, ""); - ASSERT_GE(fileIno, 0); + EXPECT_CALL(*mIncFs, makeDir(_, dir_path, _)); + auto res = mIncrementalService->makeDir(storageId, dir_path, 0555); + ASSERT_EQ(res, 0); } -TEST_F(IncrementalServiceTest, testMakeDirectoryNoParent) { +TEST_F(IncrementalServiceTest, testMakeDirectoryNested) { mVold->mountIncFsSuccess(); mIncFs->makeFileSuccess(); mVold->bindMountSuccess(); @@ -427,13 +422,15 @@ TEST_F(IncrementalServiceTest, testMakeDirectoryNoParent) { int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew); - std::string_view first("first"); - std::string_view second("second"); + auto first = "first"sv; + auto second = "second"sv; std::string dir_path = std::string(first) + "/" + std::string(second); - EXPECT_CALL(*mIncFs, makeDir(_, first, _, _, _)).Times(0); - EXPECT_CALL(*mIncFs, makeDir(_, second, _, _, _)).Times(0); - int fileIno = mIncrementalService->makeDir(storageId, dir_path, ""); - ASSERT_LT(fileIno, 0); + EXPECT_CALL(*mIncFs, makeDir(_, first, _)).Times(0); + EXPECT_CALL(*mIncFs, makeDir(_, second, _)).Times(0); + EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(dir_path), _)).Times(1); + + auto res = mIncrementalService->makeDir(storageId, dir_path, 0555); + ASSERT_EQ(res, 0); } TEST_F(IncrementalServiceTest, testMakeDirectories) { @@ -446,16 +443,18 @@ TEST_F(IncrementalServiceTest, testMakeDirectories) { int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), IncrementalService::CreateOptions::CreateNew); - std::string_view first("first"); - std::string_view second("second"); - std::string_view third("third"); + auto first = "first"sv; + auto second = "second"sv; + auto third = "third"sv; InSequence seq; - EXPECT_CALL(*mIncFs, makeDir(_, first, _, _, _)); - EXPECT_CALL(*mIncFs, makeDir(_, second, _, _, _)); - EXPECT_CALL(*mIncFs, makeDir(_, third, _, _, _)); - std::string dir_path = - std::string(first) + "/" + std::string(second) + "/" + std::string(third); - int fileIno = mIncrementalService->makeDirs(storageId, dir_path, ""); - ASSERT_GE(fileIno, 0); + auto parent_path = std::string(first) + "/" + std::string(second); + auto dir_path = parent_path + "/" + std::string(third); + EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(dir_path), _)).WillOnce(Return(-ENOENT)); + EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(parent_path), _)).WillOnce(Return(-ENOENT)); + EXPECT_CALL(*mIncFs, makeDir(_, first, _)).WillOnce(Return(0)); + EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(parent_path), _)).WillOnce(Return(0)); + EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(dir_path), _)).WillOnce(Return(0)); + auto res = mIncrementalService->makeDirs(storageId, dir_path, 0555); + ASSERT_EQ(res, 0); } } // namespace android::os::incremental diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 258d7628e502..4f5c1abae73c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1327,7 +1327,7 @@ public final class SystemServer { if (!isWatch) { t.traceBegin("StartStatusBarManagerService"); try { - statusBar = new StatusBarManagerService(context, wm); + statusBar = new StatusBarManagerService(context); ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar); } catch (Throwable e) { reportWtf("starting StatusBarManagerService", e); @@ -1724,9 +1724,11 @@ public final class SystemServer { mSystemServiceManager.startService(SensorNotificationService.class); t.traceEnd(); - t.traceBegin("StartContextHubSystemService"); - mSystemServiceManager.startService(ContextHubSystemService.class); - t.traceEnd(); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CONTEXTHUB)) { + t.traceBegin("StartContextHubSystemService"); + mSystemServiceManager.startService(ContextHubSystemService.class); + t.traceEnd(); + } t.traceBegin("StartDiskStatsService"); try { diff --git a/services/net/java/android/net/IpMemoryStore.java b/services/net/java/android/net/IpMemoryStore.java index 6f91e006c853..dcefb537d0a0 100644 --- a/services/net/java/android/net/IpMemoryStore.java +++ b/services/net/java/android/net/IpMemoryStore.java @@ -52,6 +52,11 @@ public class IpMemoryStore extends IpMemoryStoreClient { public int getInterfaceVersion() { return this.VERSION; } + + @Override + public String getInterfaceHash() { + return this.HASH; + } }); } diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java index 7f723b1c232b..a3618b47171a 100644 --- a/services/net/java/android/net/ip/IpClientUtil.java +++ b/services/net/java/android/net/ip/IpClientUtil.java @@ -189,6 +189,11 @@ public class IpClientUtil { public int getInterfaceVersion() { return this.VERSION; } + + @Override + public String getInterfaceHash() { + return this.HASH; + } } /** diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java index 9569c6e8be89..2d18a2994135 100644 --- a/services/people/java/com/android/server/people/PeopleService.java +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -16,6 +16,7 @@ package com.android.server.people; +import android.annotation.NonNull; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; import android.app.prediction.AppTarget; @@ -29,6 +30,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemService; +import com.android.server.people.data.DataManager; import java.util.List; import java.util.Map; @@ -41,6 +43,8 @@ public class PeopleService extends SystemService { private static final String TAG = "PeopleService"; + private final DataManager mDataManager; + /** * Initializes the system service. * @@ -48,6 +52,15 @@ public class PeopleService extends SystemService { */ public PeopleService(Context context) { super(context); + + mDataManager = new DataManager(context); + } + + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_SYSTEM_SERVICES_READY) { + mDataManager.initialize(); + } } @Override @@ -55,6 +68,16 @@ public class PeopleService extends SystemService { publishLocalService(PeopleServiceInternal.class, new LocalService()); } + @Override + public void onUnlockUser(@NonNull TargetUser targetUser) { + mDataManager.onUserUnlocked(targetUser.getUserIdentifier()); + } + + @Override + public void onStopUser(@NonNull TargetUser targetUser) { + mDataManager.onUserStopped(targetUser.getUserIdentifier()); + } + @VisibleForTesting final class LocalService extends PeopleServiceInternal { @@ -63,7 +86,7 @@ public class PeopleService extends SystemService { @Override public void onCreatePredictionSession(AppPredictionContext context, AppPredictionSessionId sessionId) { - mSessions.put(sessionId, new SessionInfo(context)); + mSessions.put(sessionId, new SessionInfo(context, mDataManager)); } @Override diff --git a/services/people/java/com/android/server/people/SessionInfo.java b/services/people/java/com/android/server/people/SessionInfo.java index df7cedf7626f..eb08e03c14de 100644 --- a/services/people/java/com/android/server/people/SessionInfo.java +++ b/services/people/java/com/android/server/people/SessionInfo.java @@ -24,6 +24,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Slog; +import com.android.server.people.data.DataManager; import com.android.server.people.prediction.ConversationPredictor; import java.util.List; @@ -37,9 +38,9 @@ class SessionInfo { private final RemoteCallbackList<IPredictionCallback> mCallbacks = new RemoteCallbackList<>(); - SessionInfo(AppPredictionContext predictionContext) { + SessionInfo(AppPredictionContext predictionContext, DataManager dataManager) { mConversationPredictor = new ConversationPredictor(predictionContext, - this::updatePredictions); + this::updatePredictions, dataManager); } void addCallback(IPredictionCallback callback) { diff --git a/services/people/java/com/android/server/people/data/AggregateEventHistoryImpl.java b/services/people/java/com/android/server/people/data/AggregateEventHistoryImpl.java new file mode 100644 index 000000000000..4ac346b51b22 --- /dev/null +++ b/services/people/java/com/android/server/people/data/AggregateEventHistoryImpl.java @@ -0,0 +1,95 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** An {@link EventHistory} that aggregates multiple {@link EventHistory}. */ +class AggregateEventHistoryImpl implements EventHistory { + + private final List<EventHistory> mEventHistoryList = new ArrayList<>(); + + @NonNull + @Override + public EventIndex getEventIndex(int eventType) { + for (EventHistory eventHistory : mEventHistoryList) { + EventIndex eventIndex = eventHistory.getEventIndex(eventType); + if (!eventIndex.isEmpty()) { + return eventIndex; + } + } + return EventIndex.EMPTY; + } + + @NonNull + @Override + public EventIndex getEventIndex(Set<Integer> eventTypes) { + EventIndex merged = new EventIndex(); + for (EventHistory eventHistory : mEventHistoryList) { + EventIndex eventIndex = eventHistory.getEventIndex(eventTypes); + if (!eventIndex.isEmpty()) { + merged = EventIndex.combine(merged, eventIndex); + } + } + return merged; + } + + @NonNull + @Override + public List<Event> queryEvents(Set<Integer> eventTypes, long startTime, long endTime) { + List<Event> results = new ArrayList<>(); + for (EventHistory eventHistory : mEventHistoryList) { + EventIndex eventIndex = eventHistory.getEventIndex(eventTypes); + if (eventIndex.isEmpty()) { + continue; + } + List<Event> queryResults = eventHistory.queryEvents(eventTypes, startTime, endTime); + results = combineEventLists(results, queryResults); + } + return results; + } + + void addEventHistory(EventHistory eventHistory) { + mEventHistoryList.add(eventHistory); + } + + /** + * Combines the sorted events (in chronological order) from the given 2 lists {@code lhs} + * and {@code rhs} and preserves the order. + */ + private List<Event> combineEventLists(List<Event> lhs, List<Event> rhs) { + List<Event> results = new ArrayList<>(); + int i = 0, j = 0; + while (i < lhs.size() && j < rhs.size()) { + if (lhs.get(i).getTimestamp() < rhs.get(j).getTimestamp()) { + results.add(lhs.get(i++)); + } else { + results.add(rhs.get(j++)); + } + } + if (i < lhs.size()) { + results.addAll(lhs.subList(i, lhs.size())); + } else if (j < rhs.size()) { + results.addAll(rhs.subList(j, rhs.size())); + } + return results; + } +} diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java new file mode 100644 index 000000000000..8a3a44ae9f35 --- /dev/null +++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java @@ -0,0 +1,182 @@ +/* + * 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.Nullable; +import android.annotation.WorkerThread; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; +import android.text.TextUtils; +import android.util.Slog; + +/** A helper class that queries the Contacts database. */ +class ContactsQueryHelper { + + private static final String TAG = "ContactsQueryHelper"; + + private final Context mContext; + private Uri mContactUri; + private boolean mIsStarred; + private String mPhoneNumber; + private long mLastUpdatedTimestamp; + + ContactsQueryHelper(Context context) { + mContext = context; + } + + /** + * Queries the Contacts database with the given contact URI and returns whether the query runs + * successfully. + */ + @WorkerThread + boolean query(@NonNull String contactUri) { + if (TextUtils.isEmpty(contactUri)) { + return false; + } + Uri uri = Uri.parse(contactUri); + if ("tel".equals(uri.getScheme())) { + return queryWithPhoneNumber(uri.getSchemeSpecificPart()); + } else if ("mailto".equals(uri.getScheme())) { + return queryWithEmail(uri.getSchemeSpecificPart()); + } else if (contactUri.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) { + return queryWithUri(uri); + } + return false; + } + + /** Queries the Contacts database and read the most recently updated contact. */ + @WorkerThread + boolean querySince(long sinceTime) { + final String[] projection = new String[] { + Contacts._ID, Contacts.LOOKUP_KEY, Contacts.STARRED, Contacts.HAS_PHONE_NUMBER, + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP }; + String selection = Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + " > ?"; + String[] selectionArgs = new String[] {Long.toString(sinceTime)}; + return queryContact(Contacts.CONTENT_URI, projection, selection, selectionArgs); + } + + @Nullable + Uri getContactUri() { + return mContactUri; + } + + boolean isStarred() { + return mIsStarred; + } + + @Nullable + String getPhoneNumber() { + return mPhoneNumber; + } + + long getLastUpdatedTimestamp() { + return mLastUpdatedTimestamp; + } + + private boolean queryWithPhoneNumber(String phoneNumber) { + Uri phoneUri = Uri.withAppendedPath( + ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber)); + return queryWithUri(phoneUri); + } + + private boolean queryWithEmail(String email) { + Uri emailUri = Uri.withAppendedPath( + ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI, Uri.encode(email)); + return queryWithUri(emailUri); + } + + private boolean queryWithUri(@NonNull Uri uri) { + final String[] projection = new String[] { + Contacts._ID, Contacts.LOOKUP_KEY, Contacts.STARRED, Contacts.HAS_PHONE_NUMBER }; + return queryContact(uri, projection, /* selection= */ null, /* selectionArgs= */ null); + } + + private boolean queryContact(@NonNull Uri uri, @NonNull String[] projection, + @Nullable String selection, @Nullable String[] selectionArgs) { + long contactId; + String lookupKey = null; + boolean hasPhoneNumber = false; + boolean found = false; + try (Cursor cursor = mContext.getContentResolver().query( + uri, projection, selection, selectionArgs, /* sortOrder= */ null)) { + if (cursor == null) { + Slog.w(TAG, "Cursor is null when querying contact."); + return false; + } + while (cursor.moveToNext()) { + // Contact ID + int idIndex = cursor.getColumnIndex(Contacts._ID); + contactId = cursor.getLong(idIndex); + + // Lookup key + int lookupKeyIndex = cursor.getColumnIndex(Contacts.LOOKUP_KEY); + lookupKey = cursor.getString(lookupKeyIndex); + + mContactUri = Contacts.getLookupUri(contactId, lookupKey); + + // Starred + int starredIndex = cursor.getColumnIndex(Contacts.STARRED); + mIsStarred = cursor.getInt(starredIndex) != 0; + + // Has phone number + int hasPhoneNumIndex = cursor.getColumnIndex(Contacts.HAS_PHONE_NUMBER); + hasPhoneNumber = cursor.getInt(hasPhoneNumIndex) != 0; + + // Last updated timestamp + int lastUpdatedTimestampIndex = cursor.getColumnIndex( + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP); + if (lastUpdatedTimestampIndex >= 0) { + mLastUpdatedTimestamp = cursor.getLong(lastUpdatedTimestampIndex); + } + + found = true; + } + } + if (found && lookupKey != null && hasPhoneNumber) { + return queryPhoneNumber(lookupKey); + } + return found; + } + + private boolean queryPhoneNumber(String lookupKey) { + String[] projection = new String[] { + ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }; + String selection = Contacts.LOOKUP_KEY + " = ?"; + String[] selectionArgs = new String[] { lookupKey }; + try (Cursor cursor = mContext.getContentResolver().query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, selection, + selectionArgs, /* sortOrder= */ null)) { + if (cursor == null) { + Slog.w(TAG, "Cursor is null when querying contact phone number."); + return false; + } + while (cursor.moveToNext()) { + // Phone number + int phoneNumIdx = cursor.getColumnIndex( + ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER); + if (phoneNumIdx >= 0) { + mPhoneNumber = cursor.getString(phoneNumIdx); + } + } + } + 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 new file mode 100644 index 000000000000..bb97533b3222 --- /dev/null +++ b/services/people/java/com/android/server/people/data/ConversationInfo.java @@ -0,0 +1,372 @@ +/* + * 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.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.LocusId; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutInfo.ShortcutFlags; +import android.net.Uri; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Represents a conversation that is provided by the app based on {@link ShortcutInfo}. + */ +public class ConversationInfo { + + private static final int FLAG_VIP = 1; + + private static final int FLAG_NOTIFICATION_SILENCED = 1 << 1; + + private static final int FLAG_BUBBLED = 1 << 2; + + private static final int FLAG_PERSON_IMPORTANT = 1 << 3; + + private static final int FLAG_PERSON_BOT = 1 << 4; + + private static final int FLAG_CONTACT_STARRED = 1 << 5; + + private static final int FLAG_DEMOTED = 1 << 6; + + @IntDef(flag = true, prefix = {"FLAG_"}, value = { + FLAG_VIP, + FLAG_NOTIFICATION_SILENCED, + FLAG_BUBBLED, + FLAG_PERSON_IMPORTANT, + FLAG_PERSON_BOT, + FLAG_CONTACT_STARRED, + FLAG_DEMOTED, + }) + @Retention(RetentionPolicy.SOURCE) + private @interface ConversationFlags { + } + + @NonNull + private String mShortcutId; + + @Nullable + private LocusId mLocusId; + + @Nullable + private Uri mContactUri; + + @Nullable + private String mContactPhoneNumber; + + @Nullable + private String mNotificationChannelId; + + @ShortcutFlags + private int mShortcutFlags; + + @ConversationFlags + private int mConversationFlags; + + private ConversationInfo(Builder builder) { + mShortcutId = builder.mShortcutId; + mLocusId = builder.mLocusId; + mContactUri = builder.mContactUri; + mContactPhoneNumber = builder.mContactPhoneNumber; + mNotificationChannelId = builder.mNotificationChannelId; + mShortcutFlags = builder.mShortcutFlags; + mConversationFlags = builder.mConversationFlags; + } + + @NonNull + public String getShortcutId() { + return mShortcutId; + } + + @Nullable + LocusId getLocusId() { + return mLocusId; + } + + /** The URI to look up the entry in the contacts data provider. */ + @Nullable + Uri getContactUri() { + return mContactUri; + } + + /** The phone number of the associated contact. */ + @Nullable + String getContactPhoneNumber() { + return mContactPhoneNumber; + } + + /** + * ID of the {@link android.app.NotificationChannel} where the notifications for this + * conversation are posted. + */ + @Nullable + String getNotificationChannelId() { + return mNotificationChannelId; + } + + /** Whether the shortcut for this conversation is set long-lived by the app. */ + public boolean isShortcutLongLived() { + return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED); + } + + /** Whether this conversation is marked as VIP by the user. */ + public boolean isVip() { + return hasConversationFlags(FLAG_VIP); + } + + /** Whether the notifications for this conversation should be silenced. */ + public boolean isNotificationSilenced() { + return hasConversationFlags(FLAG_NOTIFICATION_SILENCED); + } + + /** Whether the notifications for this conversation should show in bubbles. */ + public boolean isBubbled() { + return hasConversationFlags(FLAG_BUBBLED); + } + + /** + * Whether this conversation is demoted by the user. New notifications for the demoted + * conversation will not show in the conversation space. + */ + public boolean isDemoted() { + return hasConversationFlags(FLAG_DEMOTED); + } + + /** Whether the associated person is marked as important by the app. */ + public boolean isPersonImportant() { + return hasConversationFlags(FLAG_PERSON_IMPORTANT); + } + + /** Whether the associated person is marked as a bot by the app. */ + public boolean isPersonBot() { + return hasConversationFlags(FLAG_PERSON_BOT); + } + + /** Whether the associated contact is marked as starred by the user. */ + public boolean isContactStarred() { + return hasConversationFlags(FLAG_CONTACT_STARRED); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ConversationInfo)) { + return false; + } + ConversationInfo other = (ConversationInfo) obj; + return Objects.equals(mShortcutId, other.mShortcutId) + && Objects.equals(mLocusId, other.mLocusId) + && Objects.equals(mContactUri, other.mContactUri) + && Objects.equals(mContactPhoneNumber, other.mContactPhoneNumber) + && Objects.equals(mNotificationChannelId, other.mNotificationChannelId) + && mShortcutFlags == other.mShortcutFlags + && mConversationFlags == other.mConversationFlags; + } + + @Override + public int hashCode() { + return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber, + mNotificationChannelId, mShortcutFlags, mConversationFlags); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("ConversationInfo {"); + sb.append("shortcutId=").append(mShortcutId); + sb.append(", locusId=").append(mLocusId); + sb.append(", contactUri=").append(mContactUri); + sb.append(", phoneNumber=").append(mContactPhoneNumber); + sb.append(", notificationChannelId=").append(mNotificationChannelId); + sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags)); + sb.append(" ["); + if (isShortcutLongLived()) { + sb.append("Liv"); + } + sb.append("]"); + sb.append(", conversationFlags=0x").append(Integer.toHexString(mConversationFlags)); + sb.append(" ["); + if (isVip()) { + sb.append("Vip"); + } + if (isNotificationSilenced()) { + sb.append("Sil"); + } + if (isBubbled()) { + sb.append("Bub"); + } + if (isDemoted()) { + sb.append("Dem"); + } + if (isPersonImportant()) { + sb.append("Imp"); + } + if (isPersonBot()) { + sb.append("Bot"); + } + if (isContactStarred()) { + sb.append("Sta"); + } + sb.append("]}"); + return sb.toString(); + } + + private boolean hasShortcutFlags(@ShortcutFlags int flags) { + return (mShortcutFlags & flags) == flags; + } + + private boolean hasConversationFlags(@ConversationFlags int flags) { + return (mConversationFlags & flags) == flags; + } + + /** + * Builder class for {@link ConversationInfo} objects. + */ + static class Builder { + + private String mShortcutId; + + @Nullable + private LocusId mLocusId; + + @Nullable + private Uri mContactUri; + + @Nullable + private String mContactPhoneNumber; + + @Nullable + private String mNotificationChannelId; + + @ShortcutFlags + private int mShortcutFlags; + + @ConversationFlags + private int mConversationFlags; + + Builder() { + } + + Builder(@NonNull ConversationInfo conversationInfo) { + if (mShortcutId == null) { + mShortcutId = conversationInfo.mShortcutId; + } else { + Preconditions.checkArgument(mShortcutId.equals(conversationInfo.mShortcutId)); + } + mLocusId = conversationInfo.mLocusId; + mContactUri = conversationInfo.mContactUri; + mContactPhoneNumber = conversationInfo.mContactPhoneNumber; + mNotificationChannelId = conversationInfo.mNotificationChannelId; + mShortcutFlags = conversationInfo.mShortcutFlags; + mConversationFlags = conversationInfo.mConversationFlags; + } + + Builder setShortcutId(@NonNull String shortcutId) { + mShortcutId = shortcutId; + return this; + } + + Builder setLocusId(LocusId locusId) { + mLocusId = locusId; + return this; + } + + Builder setContactUri(Uri contactUri) { + mContactUri = contactUri; + return this; + } + + Builder setContactPhoneNumber(String phoneNumber) { + mContactPhoneNumber = phoneNumber; + return this; + } + + Builder setNotificationChannelId(String notificationChannelId) { + mNotificationChannelId = notificationChannelId; + return this; + } + + Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) { + mShortcutFlags = shortcutFlags; + return this; + } + + Builder setConversationFlags(@ConversationFlags int conversationFlags) { + mConversationFlags = conversationFlags; + return this; + } + + Builder setVip(boolean value) { + return setConversationFlag(FLAG_VIP, value); + } + + Builder setNotificationSilenced(boolean value) { + return setConversationFlag(FLAG_NOTIFICATION_SILENCED, value); + } + + Builder setBubbled(boolean value) { + return setConversationFlag(FLAG_BUBBLED, value); + } + + Builder setDemoted(boolean value) { + return setConversationFlag(FLAG_DEMOTED, value); + } + + Builder setPersonImportant(boolean value) { + return setConversationFlag(FLAG_PERSON_IMPORTANT, value); + } + + Builder setPersonBot(boolean value) { + return setConversationFlag(FLAG_PERSON_BOT, value); + } + + Builder setContactStarred(boolean value) { + return setConversationFlag(FLAG_CONTACT_STARRED, value); + } + + private Builder setConversationFlag(@ConversationFlags int flags, boolean value) { + if (value) { + return addConversationFlags(flags); + } else { + return removeConversationFlags(flags); + } + } + + private Builder addConversationFlags(@ConversationFlags int flags) { + mConversationFlags |= flags; + return this; + } + + private Builder removeConversationFlags(@ConversationFlags int flags) { + mConversationFlags &= ~flags; + return this; + } + + ConversationInfo build() { + Objects.requireNonNull(mShortcutId); + return new ConversationInfo(this); + } + } +} diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java new file mode 100644 index 000000000000..f17e1b91cb5d --- /dev/null +++ b/services/people/java/com/android/server/people/data/ConversationStore.java @@ -0,0 +1,109 @@ +/* + * 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.Nullable; +import android.content.LocusId; +import android.net.Uri; +import android.util.ArrayMap; + +import java.util.Map; +import java.util.function.Consumer; + +/** The store that stores and accesses the conversations data for a package. */ +class ConversationStore { + + // Shortcut ID -> Conversation Info + private final Map<String, ConversationInfo> mConversationInfoMap = new ArrayMap<>(); + + // Locus ID -> Shortcut ID + private final Map<LocusId, String> mLocusIdToShortcutIdMap = new ArrayMap<>(); + + // Contact URI -> Shortcut ID + private final Map<Uri, String> mContactUriToShortcutIdMap = new ArrayMap<>(); + + // Phone Number -> Shortcut ID + private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>(); + + void addOrUpdate(@NonNull ConversationInfo conversationInfo) { + mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo); + + LocusId locusId = conversationInfo.getLocusId(); + if (locusId != null) { + mLocusIdToShortcutIdMap.put(locusId, conversationInfo.getShortcutId()); + } + + Uri contactUri = conversationInfo.getContactUri(); + if (contactUri != null) { + mContactUriToShortcutIdMap.put(contactUri, conversationInfo.getShortcutId()); + } + + String phoneNumber = conversationInfo.getContactPhoneNumber(); + if (phoneNumber != null) { + mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId()); + } + } + + void deleteConversation(@NonNull String shortcutId) { + ConversationInfo conversationInfo = mConversationInfoMap.remove(shortcutId); + if (conversationInfo == null) { + return; + } + + LocusId locusId = conversationInfo.getLocusId(); + if (locusId != null) { + mLocusIdToShortcutIdMap.remove(locusId); + } + + Uri contactUri = conversationInfo.getContactUri(); + if (contactUri != null) { + mContactUriToShortcutIdMap.remove(contactUri); + } + + String phoneNumber = conversationInfo.getContactPhoneNumber(); + if (phoneNumber != null) { + mPhoneNumberToShortcutIdMap.remove(phoneNumber); + } + } + + void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { + for (ConversationInfo ci : mConversationInfoMap.values()) { + consumer.accept(ci); + } + } + + @Nullable + ConversationInfo getConversation(@Nullable String shortcutId) { + return shortcutId != null ? mConversationInfoMap.get(shortcutId) : null; + } + + @Nullable + ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) { + return getConversation(mLocusIdToShortcutIdMap.get(locusId)); + } + + @Nullable + ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) { + return getConversation(mContactUriToShortcutIdMap.get(contactUri)); + } + + @Nullable + ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { + return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber)); + } +} diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java new file mode 100644 index 000000000000..13cce414ea7c --- /dev/null +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -0,0 +1,559 @@ +/* + * 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.Nullable; +import android.annotation.UserIdInt; +import android.annotation.WorkerThread; +import android.app.Notification; +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; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.LauncherApps.ShortcutQuery; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.content.pm.ShortcutManager.ShareShortcutInfo; +import android.content.pm.ShortcutServiceInternal; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Binder; +import android.os.Handler; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.ContactsContract.Contacts; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.telecom.TelecomManager; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.ChooserActivity; +import com.android.internal.os.BackgroundThread; +import com.android.internal.telephony.SmsApplication; +import com.android.server.LocalServices; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * A class manages the lifecycle of the conversations and associated data, and exposes the methods + * to access the data in People Service and other system services. + */ +public class DataManager { + + private static final String PLATFORM_PACKAGE_NAME = "android"; + private static final int MY_UID = Process.myUid(); + private static final int MY_PID = Process.myPid(); + private static final long USAGE_STATS_QUERY_MAX_EVENT_AGE_MS = DateUtils.DAY_IN_MILLIS; + private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L; + + private final Context mContext; + private final Injector mInjector; + private final ScheduledExecutorService mUsageStatsQueryExecutor; + + private final SparseArray<UserData> mUserDataArray = new SparseArray<>(); + private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>(); + private final SparseArray<ContentObserver> mContactsContentObservers = new SparseArray<>(); + private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>(); + private final SparseArray<NotificationListenerService> mNotificationListeners = + new SparseArray<>(); + + private ShortcutServiceInternal mShortcutServiceInternal; + private UsageStatsManagerInternal mUsageStatsManagerInternal; + private ShortcutManager mShortcutManager; + private UserManager mUserManager; + + public DataManager(Context context) { + mContext = context; + mInjector = new Injector(); + mUsageStatsQueryExecutor = mInjector.createScheduledExecutor(); + } + + @VisibleForTesting + DataManager(Context context, Injector injector) { + mContext = context; + mInjector = injector; + mUsageStatsQueryExecutor = mInjector.createScheduledExecutor(); + } + + /** 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); + + mShortcutServiceInternal.addListener(new ShortcutServiceListener()); + } + + /** This method is called when a user is unlocked. */ + public void onUserUnlocked(int userId) { + UserData userData = mUserDataArray.get(userId); + if (userData == null) { + userData = new UserData(userId); + mUserDataArray.put(userId, userData); + } + userData.setUserUnlocked(); + updateDefaultDialer(userData); + updateDefaultSmsApp(userData); + + ScheduledFuture<?> scheduledFuture = mUsageStatsQueryExecutor.scheduleAtFixedRate( + new UsageStatsQueryRunnable(userId), 1L, USAGE_STATS_QUERY_INTERVAL_SEC, + TimeUnit.SECONDS); + mUsageStatsQueryFutures.put(userId, scheduledFuture); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED); + intentFilter.addAction(SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL); + BroadcastReceiver broadcastReceiver = new PerUserBroadcastReceiver(userId); + mBroadcastReceivers.put(userId, broadcastReceiver); + mContext.registerReceiverAsUser( + broadcastReceiver, UserHandle.of(userId), intentFilter, null, null); + + ContentObserver contactsContentObserver = new ContactsContentObserver( + BackgroundThread.getHandler()); + mContactsContentObservers.put(userId, contactsContentObserver); + mContext.getContentResolver().registerContentObserver( + Contacts.CONTENT_URI, /* notifyForDescendants= */ true, + contactsContentObserver, userId); + + NotificationListener notificationListener = new NotificationListener(); + mNotificationListeners.put(userId, notificationListener); + try { + notificationListener.registerAsSystemService(mContext, + new ComponentName(PLATFORM_PACKAGE_NAME, getClass().getSimpleName()), + UserHandle.myUserId()); + } catch (RemoteException e) { + // Should never occur for local calls. + } + } + + /** This method is called when a user is stopped. */ + public void onUserStopped(int userId) { + if (mUserDataArray.indexOfKey(userId) >= 0) { + mUserDataArray.get(userId).setUserStopped(); + } + if (mUsageStatsQueryFutures.indexOfKey(userId) >= 0) { + mUsageStatsQueryFutures.valueAt(userId).cancel(true); + } + if (mBroadcastReceivers.indexOfKey(userId) >= 0) { + mContext.unregisterReceiver(mBroadcastReceivers.get(userId)); + } + if (mContactsContentObservers.indexOfKey(userId) >= 0) { + mContext.getContentResolver().unregisterContentObserver( + mContactsContentObservers.get(userId)); + } + if (mNotificationListeners.indexOfKey(userId) >= 0) { + try { + mNotificationListeners.get(userId).unregisterAsSystemService(); + } catch (RemoteException e) { + // Should never occur for local calls. + } + } + } + + /** + * Iterates through all the {@link PackageData}s owned by the unlocked users who are in the + * same profile group as the calling user. + */ + public void forAllPackages(Consumer<PackageData> consumer) { + List<UserInfo> users = mUserManager.getEnabledProfiles(mInjector.getCallingUserId()); + for (UserInfo userInfo : users) { + UserData userData = getUnlockedUserData(userInfo.id); + if (userData != null) { + userData.forAllPackages(consumer); + } + } + } + + /** Gets the {@link ShortcutInfo} for the given shortcut ID. */ + @Nullable + public ShortcutInfo getShortcut(@NonNull String packageName, @UserIdInt int userId, + @NonNull String shortcutId) { + List<ShortcutInfo> shortcuts = getShortcuts(packageName, userId, + Collections.singletonList(shortcutId)); + if (shortcuts != null && !shortcuts.isEmpty()) { + return shortcuts.get(0); + } + return null; + } + + /** + * Gets the conversation {@link ShareShortcutInfo}s from all packages owned by the calling user + * that match the specified {@link IntentFilter}. + */ + public List<ShareShortcutInfo> getConversationShareTargets( + @NonNull IntentFilter intentFilter) { + List<ShareShortcutInfo> shareShortcuts = mShortcutManager.getShareTargets(intentFilter); + List<ShareShortcutInfo> result = new ArrayList<>(); + for (ShareShortcutInfo shareShortcut : shareShortcuts) { + ShortcutInfo si = shareShortcut.getShortcutInfo(); + if (getConversationInfo(si.getPackage(), si.getUserId(), si.getId()) != null) { + result.add(shareShortcut); + } + } + return result; + } + + /** Reports the {@link AppTargetEvent} from App Prediction Manager. */ + public void reportAppTargetEvent(@NonNull AppTargetEvent event, + @Nullable IntentFilter intentFilter) { + AppTarget appTarget = event.getTarget(); + ShortcutInfo shortcutInfo = appTarget != null ? appTarget.getShortcutInfo() : null; + if (shortcutInfo == null || event.getAction() != AppTargetEvent.ACTION_LAUNCH) { + return; + } + PackageData packageData = getPackageData(appTarget.getPackageName(), + appTarget.getUser().getIdentifier()); + if (packageData == null) { + return; + } + if (ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE.equals(event.getLaunchLocation())) { + String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null; + String shortcutId = shortcutInfo.getId(); + if (packageData.getConversationStore().getConversation(shortcutId) == null + || TextUtils.isEmpty(mimeType)) { + return; + } + EventHistoryImpl eventHistory = + packageData.getEventStore().getOrCreateShortcutEventHistory( + shortcutInfo.getId()); + @Event.EventType int eventType; + if (mimeType.startsWith("text/")) { + eventType = Event.TYPE_SHARE_TEXT; + } else if (mimeType.startsWith("image/")) { + eventType = Event.TYPE_SHARE_IMAGE; + } else if (mimeType.startsWith("video/")) { + eventType = Event.TYPE_SHARE_VIDEO; + } else { + eventType = Event.TYPE_SHARE_OTHER; + } + eventHistory.addEvent(new Event(System.currentTimeMillis(), eventType)); + } + } + + /** Gets a list of {@link ShortcutInfo}s with the given shortcut IDs. */ + private List<ShortcutInfo> getShortcuts( + @NonNull String packageName, @UserIdInt int userId, + @Nullable List<String> shortcutIds) { + @ShortcutQuery.QueryFlags int queryFlags = ShortcutQuery.FLAG_MATCH_DYNAMIC + | 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); + } + + @Nullable + private UserData getUnlockedUserData(int userId) { + UserData userData = mUserDataArray.get(userId); + return userData != null && userData.isUnlocked() ? userData : null; + } + + @Nullable + private PackageData getPackageData(@NonNull String packageName, int userId) { + UserData userData = getUnlockedUserData(userId); + return userData != null ? userData.getPackageData(packageName) : null; + } + + @Nullable + private ConversationInfo getConversationInfo(@NonNull String packageName, @UserIdInt int userId, + @NonNull String shortcutId) { + PackageData packageData = getPackageData(packageName, userId); + return packageData != null ? packageData.getConversationStore().getConversation(shortcutId) + : null; + } + + private void updateDefaultDialer(@NonNull UserData userData) { + TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class); + String defaultDialer = telecomManager != null + ? telecomManager.getDefaultDialerPackage(userData.getUserId()) : null; + userData.setDefaultDialer(defaultDialer); + } + + private void updateDefaultSmsApp(@NonNull UserData userData) { + ComponentName component = SmsApplication.getDefaultSmsApplicationAsUser( + mContext, /* updateIfNeeded= */ false, userData.getUserId()); + String defaultSmsApp = component != null ? component.getPackageName() : null; + userData.setDefaultSmsApp(defaultSmsApp); + } + + @Nullable + private EventHistoryImpl getEventHistoryIfEligible(StatusBarNotification sbn) { + Notification notification = sbn.getNotification(); + String shortcutId = notification.getShortcutId(); + if (shortcutId == null) { + return null; + } + PackageData packageData = getPackageData(sbn.getPackageName(), + sbn.getUser().getIdentifier()); + if (packageData == null + || packageData.getConversationStore().getConversation(shortcutId) == null) { + return null; + } + return packageData.getEventStore().getOrCreateShortcutEventHistory(shortcutId); + } + + @VisibleForTesting + @WorkerThread + void onShortcutAddedOrUpdated(@NonNull ShortcutInfo shortcutInfo) { + if (shortcutInfo.getPersons() == null || shortcutInfo.getPersons().length == 0) { + return; + } + UserData userData = getUnlockedUserData(shortcutInfo.getUserId()); + if (userData == null) { + return; + } + PackageData packageData = userData.getOrCreatePackageData(shortcutInfo.getPackage()); + ConversationStore conversationStore = packageData.getConversationStore(); + ConversationInfo oldConversationInfo = + conversationStore.getConversation(shortcutInfo.getId()); + ConversationInfo.Builder builder = oldConversationInfo != null + ? new ConversationInfo.Builder(oldConversationInfo) + : new ConversationInfo.Builder(); + + builder.setShortcutId(shortcutInfo.getId()); + builder.setLocusId(shortcutInfo.getLocusId()); + builder.setShortcutFlags(shortcutInfo.getFlags()); + + Person person = shortcutInfo.getPersons()[0]; + builder.setPersonImportant(person.isImportant()); + builder.setPersonBot(person.isBot()); + String contactUri = person.getUri(); + if (contactUri != null) { + ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext); + if (helper.query(contactUri)) { + builder.setContactUri(helper.getContactUri()); + builder.setContactStarred(helper.isStarred()); + builder.setContactPhoneNumber(helper.getPhoneNumber()); + } + } else { + builder.setContactUri(null); + builder.setContactPhoneNumber(null); + builder.setContactStarred(false); + } + + conversationStore.addOrUpdate(builder.build()); + } + + @VisibleForTesting + @WorkerThread + void queryUsageStatsService(@UserIdInt int userId, long currentTime, long lastQueryTime) { + UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser( + userId, lastQueryTime, currentTime, false); + if (usageEvents == null) { + return; + } + while (usageEvents.hasNextEvent()) { + UsageEvents.Event e = new UsageEvents.Event(); + usageEvents.getNextEvent(e); + + String packageName = e.getPackageName(); + PackageData packageData = getPackageData(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); + } + + @VisibleForTesting + NotificationListenerService getNotificationListenerServiceForTesting(@UserIdInt int userId) { + return mNotificationListeners.get(userId); + } + + /** Observer that observes the changes in the Contacts database. */ + private class ContactsContentObserver extends ContentObserver { + + private long mLastUpdatedTimestamp; + + private ContactsContentObserver(Handler handler) { + super(handler); + mLastUpdatedTimestamp = System.currentTimeMillis(); + } + + @Override + public void onChange(boolean selfChange, Uri uri, @UserIdInt int userId) { + ContactsQueryHelper helper = mInjector.createContactsQueryHelper(mContext); + if (!helper.querySince(mLastUpdatedTimestamp)) { + return; + } + Uri contactUri = helper.getContactUri(); + + final ConversationSelector conversationSelector = new ConversationSelector(); + UserData userData = getUnlockedUserData(userId); + if (userData == null) { + return; + } + userData.forAllPackages(packageData -> { + ConversationInfo ci = + packageData.getConversationStore().getConversationByContactUri(contactUri); + if (ci != null) { + conversationSelector.mConversationStore = + packageData.getConversationStore(); + conversationSelector.mConversationInfo = ci; + } + }); + if (conversationSelector.mConversationInfo == null) { + return; + } + + ConversationInfo.Builder builder = + new ConversationInfo.Builder(conversationSelector.mConversationInfo); + builder.setContactStarred(helper.isStarred()); + builder.setContactPhoneNumber(helper.getPhoneNumber()); + conversationSelector.mConversationStore.addOrUpdate(builder.build()); + mLastUpdatedTimestamp = helper.getLastUpdatedTimestamp(); + } + + private class ConversationSelector { + private ConversationStore mConversationStore = null; + private ConversationInfo mConversationInfo = null; + } + } + + /** Listener for the shortcut data changes. */ + private class ShortcutServiceListener implements + ShortcutServiceInternal.ShortcutChangeListener { + + @Override + public void onShortcutChanged(@NonNull String packageName, int userId) { + BackgroundThread.getExecutor().execute(() -> { + List<ShortcutInfo> shortcuts = getShortcuts(packageName, userId, + /*shortcutIds=*/ null); + for (ShortcutInfo shortcut : shortcuts) { + onShortcutAddedOrUpdated(shortcut); + } + }); + } + } + + /** Listener for the notifications and their settings changes. */ + private class NotificationListener extends NotificationListenerService { + + @Override + public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, + int reason) { + if (reason != REASON_CLICK) { + return; + } + EventHistoryImpl eventHistory = getEventHistoryIfEligible(sbn); + if (eventHistory == null) { + return; + } + long currentTime = System.currentTimeMillis(); + eventHistory.addEvent(new Event(currentTime, Event.TYPE_NOTIFICATION_OPENED)); + } + } + + /** + * A {@link Runnable} that queries the Usage Stats Service for recent events for a specified + * user. + */ + private class UsageStatsQueryRunnable implements Runnable { + + private final int mUserId; + private long mLastQueryTime; + + private UsageStatsQueryRunnable(int userId) { + mUserId = userId; + mLastQueryTime = System.currentTimeMillis() - USAGE_STATS_QUERY_MAX_EVENT_AGE_MS; + } + + @Override + public void run() { + long currentTime = System.currentTimeMillis(); + queryUsageStatsService(mUserId, currentTime, mLastQueryTime); + mLastQueryTime = currentTime; + } + } + + /** A {@link BroadcastReceiver} that receives the intents for a specified user. */ + private class PerUserBroadcastReceiver extends BroadcastReceiver { + + private final int mUserId; + + private PerUserBroadcastReceiver(int userId) { + mUserId = userId; + } + + @Override + public void onReceive(Context context, Intent intent) { + UserData userData = getUnlockedUserData(mUserId); + if (userData == null) { + return; + } + if (TelecomManager.ACTION_DEFAULT_DIALER_CHANGED.equals(intent.getAction())) { + String defaultDialer = intent.getStringExtra( + TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME); + userData.setDefaultDialer(defaultDialer); + } else if (SmsApplication.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL.equals( + intent.getAction())) { + updateDefaultSmsApp(userData); + } + } + } + + @VisibleForTesting + static class Injector { + + ScheduledExecutorService createScheduledExecutor() { + return Executors.newSingleThreadScheduledExecutor(); + } + + ContactsQueryHelper createContactsQueryHelper(Context context) { + return new ContactsQueryHelper(context); + } + + 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 new file mode 100644 index 000000000000..c2364a295e30 --- /dev/null +++ b/services/people/java/com/android/server/people/data/Event.java @@ -0,0 +1,195 @@ +/* + * 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.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.Set; + +/** An event representing the interaction with a specific conversation or app. */ +public class Event { + + public static final int TYPE_SHORTCUT_INVOCATION = 1; + + public static final int TYPE_NOTIFICATION_POSTED = 2; + + public static final int TYPE_NOTIFICATION_OPENED = 3; + + public static final int TYPE_SHARE_TEXT = 4; + + public static final int TYPE_SHARE_IMAGE = 5; + + public static final int TYPE_SHARE_VIDEO = 6; + + public static final int TYPE_SHARE_OTHER = 7; + + public static final int TYPE_SMS_OUTGOING = 8; + + public static final int TYPE_SMS_INCOMING = 9; + + public static final int TYPE_CALL_OUTGOING = 10; + + public static final int TYPE_CALL_INCOMING = 11; + + public static final int TYPE_CALL_MISSED = 12; + + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_SHORTCUT_INVOCATION, + TYPE_NOTIFICATION_POSTED, + TYPE_NOTIFICATION_OPENED, + TYPE_SHARE_TEXT, + TYPE_SHARE_IMAGE, + TYPE_SHARE_VIDEO, + TYPE_SHARE_OTHER, + TYPE_SMS_OUTGOING, + TYPE_SMS_INCOMING, + TYPE_CALL_OUTGOING, + TYPE_CALL_INCOMING, + TYPE_CALL_MISSED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EventType {} + + public static final Set<Integer> NOTIFICATION_EVENT_TYPES = new ArraySet<>(); + public static final Set<Integer> SHARE_EVENT_TYPES = new ArraySet<>(); + public static final Set<Integer> SMS_EVENT_TYPES = new ArraySet<>(); + public static final Set<Integer> CALL_EVENT_TYPES = new ArraySet<>(); + public static final Set<Integer> ALL_EVENT_TYPES = new ArraySet<>(); + + static { + NOTIFICATION_EVENT_TYPES.add(TYPE_NOTIFICATION_POSTED); + NOTIFICATION_EVENT_TYPES.add(TYPE_NOTIFICATION_OPENED); + + SHARE_EVENT_TYPES.add(TYPE_SHARE_TEXT); + SHARE_EVENT_TYPES.add(TYPE_SHARE_IMAGE); + SHARE_EVENT_TYPES.add(TYPE_SHARE_VIDEO); + SHARE_EVENT_TYPES.add(TYPE_SHARE_OTHER); + + SMS_EVENT_TYPES.add(TYPE_SMS_INCOMING); + SMS_EVENT_TYPES.add(TYPE_SMS_OUTGOING); + + CALL_EVENT_TYPES.add(TYPE_CALL_INCOMING); + CALL_EVENT_TYPES.add(TYPE_CALL_OUTGOING); + CALL_EVENT_TYPES.add(TYPE_CALL_MISSED); + + ALL_EVENT_TYPES.add(TYPE_SHORTCUT_INVOCATION); + ALL_EVENT_TYPES.addAll(NOTIFICATION_EVENT_TYPES); + ALL_EVENT_TYPES.addAll(SHARE_EVENT_TYPES); + ALL_EVENT_TYPES.addAll(SMS_EVENT_TYPES); + ALL_EVENT_TYPES.addAll(CALL_EVENT_TYPES); + } + + private final long mTimestamp; + + private final int mType; + + private final CallDetails mCallDetails; + + Event(long timestamp, @EventType int type) { + mTimestamp = timestamp; + mType = type; + mCallDetails = null; + } + + private Event(@NonNull Builder builder) { + mTimestamp = builder.mTimestamp; + mType = builder.mType; + mCallDetails = builder.mCallDetails; + } + + public long getTimestamp() { + return mTimestamp; + } + + public @EventType int getType() { + return mType; + } + + /** + * 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}. + */ + @Nullable + public CallDetails getCallDetails() { + return mCallDetails; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + 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); + } + 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 { + + private final long mTimestamp; + + private final int mType; + + private CallDetails mCallDetails; + + Builder(long timestamp, @EventType int type) { + mTimestamp = timestamp; + mType = type; + } + + Builder setCallDetails(CallDetails callDetails) { + Preconditions.checkArgument(CALL_EVENT_TYPES.contains(mType)); + mCallDetails = callDetails; + return this; + } + + Event build() { + return new Event(this); + } + } +} diff --git a/services/people/java/com/android/server/people/data/EventHistory.java b/services/people/java/com/android/server/people/data/EventHistory.java new file mode 100644 index 000000000000..5b11fd0caf05 --- /dev/null +++ b/services/people/java/com/android/server/people/data/EventHistory.java @@ -0,0 +1,44 @@ +/* + * 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 java.util.List; +import java.util.Set; + +/** The interface for querying the event time distribution and details. */ +public interface EventHistory { + + /** Gets the {@link EventIndex} for the specified event type. */ + @NonNull + EventIndex getEventIndex(@Event.EventType int eventType); + + /** Gets the combined {@link EventIndex} for a set of event types. */ + @NonNull + EventIndex getEventIndex(Set<Integer> eventTypes); + + /** + * Returns a {@link List} of {@link Event}s those timestamps are between the specified {@code + * fromTimestamp}, inclusive, and {@code toTimestamp} exclusive, and match the specified event + * types. + * + * @return a list of matched events in chronological order. + */ + @NonNull + List<Event> queryEvents(Set<Integer> eventTypes, long fromTimestamp, long toTimestamp); +} diff --git a/services/people/java/com/android/server/people/data/EventHistoryImpl.java b/services/people/java/com/android/server/people/data/EventHistoryImpl.java new file mode 100644 index 000000000000..6b6bd7e3cfb0 --- /dev/null +++ b/services/people/java/com/android/server/people/data/EventHistoryImpl.java @@ -0,0 +1,88 @@ +/* + * 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.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.List; +import java.util.Set; + +class EventHistoryImpl implements EventHistory { + + private final Injector mInjector; + + // Event Type -> Event Index + private final SparseArray<EventIndex> mEventIndexArray = new SparseArray<>(); + + private final EventList mRecentEvents = new EventList(); + + EventHistoryImpl() { + mInjector = new Injector(); + } + + @VisibleForTesting + EventHistoryImpl(Injector injector) { + mInjector = injector; + } + + @Override + @NonNull + public EventIndex getEventIndex(@Event.EventType int eventType) { + EventIndex eventIndex = mEventIndexArray.get(eventType); + return eventIndex != null ? new EventIndex(eventIndex) : mInjector.createEventIndex(); + } + + @Override + @NonNull + public EventIndex getEventIndex(Set<Integer> eventTypes) { + EventIndex combined = mInjector.createEventIndex(); + for (@Event.EventType int eventType : eventTypes) { + EventIndex eventIndex = mEventIndexArray.get(eventType); + if (eventIndex != null) { + combined = EventIndex.combine(combined, eventIndex); + } + } + return combined; + } + + @Override + @NonNull + public List<Event> queryEvents(Set<Integer> eventTypes, long startTime, long endTime) { + return mRecentEvents.queryEvents(eventTypes, startTime, endTime); + } + + void addEvent(Event event) { + EventIndex eventIndex = mEventIndexArray.get(event.getType()); + if (eventIndex == null) { + eventIndex = mInjector.createEventIndex(); + mEventIndexArray.put(event.getType(), eventIndex); + } + eventIndex.addEvent(event.getTimestamp()); + mRecentEvents.add(event); + } + + @VisibleForTesting + static class Injector { + + EventIndex createEventIndex() { + return new EventIndex(); + } + } +} diff --git a/services/people/java/com/android/server/people/data/EventIndex.java b/services/people/java/com/android/server/people/data/EventIndex.java new file mode 100644 index 000000000000..b74a3fae98a5 --- /dev/null +++ b/services/people/java/com/android/server/people/data/EventIndex.java @@ -0,0 +1,377 @@ +/* + * 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.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.format.DateFormat; +import android.util.Range; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.TimeZone; +import java.util.function.Function; + +/** + * The index of {@link Event}s. It is used for quickly looking up the time distribution of + * {@link Event}s based on {@code Event#getTimestamp()}. + * + * <p>The 64-bits {code long} is used as the bitmap index. Each bit is to denote whether there are + * any events in a specified time slot. The least significant bit is for the most recent time slot. + * And the most significant bit is for the oldest time slot. + * + * <p>Multiple {code long}s are used to index the events in different time grains. For the recent + * events, the fine-grained bitmap index can provide the narrower time range. For the older events, + * the coarse-grained bitmap index can cover longer period but can only provide wider time range. + * + * <p>E.g. the below chart shows how the bitmap indexes index the events in the past 24 hours: + * <pre> + * 2020/1/3 2020/1/4 + * 0:00 4:00 8:00 12:00 16:00 20:00 0:00 + * --+-----------------------------------------------------------------------+- 1 day per bit + * --+-----------+-----------+-----------+-----------+-----------+-----------+- 4 hours per bit + * --+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+- 1 hour per bit + * +++++++++ 2 minutes per bit + * </pre> + */ +public class EventIndex { + + private static final int LONG_SIZE_BITS = 64; + + private static final int TIME_SLOT_ONE_DAY = 0; + + private static final int TIME_SLOT_FOUR_HOURS = 1; + + private static final int TIME_SLOT_ONE_HOUR = 2; + + private static final int TIME_SLOT_TWO_MINUTES = 3; + + @IntDef(prefix = {"TIME_SLOT_"}, value = { + TIME_SLOT_ONE_DAY, + TIME_SLOT_FOUR_HOURS, + TIME_SLOT_ONE_HOUR, + TIME_SLOT_TWO_MINUTES, + }) + @Retention(RetentionPolicy.SOURCE) + private @interface TimeSlotType { + } + + private static final int TIME_SLOT_TYPES_COUNT = 4; + + static final EventIndex EMPTY = new EventIndex(); + + private static final List<Function<Long, Range<Long>>> TIME_SLOT_FACTORIES = + Collections.unmodifiableList( + Arrays.asList( + EventIndex::createOneDayLongTimeSlot, + EventIndex::createFourHoursLongTimeSlot, + EventIndex::createOneHourLongTimeSlot, + EventIndex::createTwoMinutesLongTimeSlot + ) + ); + + /** Combines the two {@link EventIndex} objects and returns the combined result. */ + static EventIndex combine(EventIndex lhs, EventIndex rhs) { + EventIndex older = lhs.mLastUpdatedTime < rhs.mLastUpdatedTime ? lhs : rhs; + EventIndex younger = lhs.mLastUpdatedTime >= rhs.mLastUpdatedTime ? lhs : rhs; + + EventIndex combined = new EventIndex(older); + combined.updateEventBitmaps(younger.mLastUpdatedTime); + + for (int slotType = 0; slotType < TIME_SLOT_TYPES_COUNT; slotType++) { + combined.mEventBitmaps[slotType] |= younger.mEventBitmaps[slotType]; + } + return combined; + } + + private final long[] mEventBitmaps; + + private long mLastUpdatedTime; + + private final Object mLock = new Object(); + + private final Injector mInjector; + + EventIndex() { + mInjector = new Injector(); + mEventBitmaps = new long[]{0L, 0L, 0L, 0L}; + mLastUpdatedTime = mInjector.currentTimeMillis(); + } + + EventIndex(EventIndex from) { + mInjector = new Injector(); + mEventBitmaps = Arrays.copyOf(from.mEventBitmaps, TIME_SLOT_TYPES_COUNT); + mLastUpdatedTime = from.mLastUpdatedTime; + } + + @VisibleForTesting + EventIndex(Injector injector) { + mInjector = injector; + mEventBitmaps = new long[]{0L, 0L, 0L, 0L}; + mLastUpdatedTime = mInjector.currentTimeMillis(); + } + + /** + * Gets the most recent active time slot. A time slot is active if there is at least one event + * occurred in that time slot. + */ + @Nullable + public Range<Long> getMostRecentActiveTimeSlot() { + synchronized (mLock) { + for (int slotType = TIME_SLOT_TYPES_COUNT - 1; slotType >= 0; slotType--) { + if (mEventBitmaps[slotType] == 0L) { + continue; + } + Range<Long> lastTimeSlot = + TIME_SLOT_FACTORIES.get(slotType).apply(mLastUpdatedTime); + int numberOfTrailingZeros = Long.numberOfTrailingZeros(mEventBitmaps[slotType]); + long offset = getDuration(lastTimeSlot) * numberOfTrailingZeros; + return Range.create(lastTimeSlot.getLower() - offset, + lastTimeSlot.getUpper() - offset); + } + } + return null; + } + + /** + * Gets the active time slots. A time slot is active if there is at least one event occurred + * in that time slot. + * + * @return active time slots in chronological order. + */ + @NonNull + public List<Range<Long>> getActiveTimeSlots() { + List<Range<Long>> activeTimeSlots = new ArrayList<>(); + synchronized (mLock) { + for (int slotType = 0; slotType < TIME_SLOT_TYPES_COUNT; slotType++) { + activeTimeSlots = combineTimeSlotLists(activeTimeSlots, + getActiveTimeSlotsForType(slotType)); + } + } + Collections.reverse(activeTimeSlots); + return activeTimeSlots; + } + + /** Returns whether this {@link EventIndex} instance is empty. */ + public boolean isEmpty() { + synchronized (mLock) { + for (int slotType = 0; slotType < TIME_SLOT_TYPES_COUNT; slotType++) { + if (mEventBitmaps[slotType] != 0L) { + return false; + } + } + } + return true; + } + + /** + * Adds an event to this index with the given event time. Before the new event is recorded, the + * index is updated first with the current timestamp. + */ + void addEvent(long eventTime) { + if (EMPTY == this) { + throw new IllegalStateException("EMPTY instance is immutable"); + } + synchronized (mLock) { + long currentTime = mInjector.currentTimeMillis(); + updateEventBitmaps(currentTime); + for (int slotType = 0; slotType < TIME_SLOT_TYPES_COUNT; slotType++) { + int offset = diffTimeSlots(slotType, eventTime, currentTime); + if (offset < LONG_SIZE_BITS) { + mEventBitmaps[slotType] |= (1L << offset); + } + } + } + } + + /** Updates to make all bitmaps up to date. */ + void update() { + updateEventBitmaps(mInjector.currentTimeMillis()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("EventIndex {"); + sb.append("perDayEventBitmap=0b"); + sb.append(Long.toBinaryString(mEventBitmaps[TIME_SLOT_ONE_DAY])); + sb.append(", perFourHoursEventBitmap=0b"); + sb.append(Long.toBinaryString(mEventBitmaps[TIME_SLOT_FOUR_HOURS])); + sb.append(", perHourEventBitmap=0b"); + sb.append(Long.toBinaryString(mEventBitmaps[TIME_SLOT_ONE_HOUR])); + sb.append(", perTwoMinutesEventBitmap=0b"); + sb.append(Long.toBinaryString(mEventBitmaps[TIME_SLOT_TWO_MINUTES])); + sb.append(", lastUpdatedTime="); + sb.append(DateFormat.format("yyyy-MM-dd HH:mm:ss", mLastUpdatedTime)); + sb.append("}"); + return sb.toString(); + } + + /** Shifts the event bitmaps to make them up-to-date. */ + private void updateEventBitmaps(long currentTimeMillis) { + for (int slotType = 0; slotType < TIME_SLOT_TYPES_COUNT; slotType++) { + int offset = diffTimeSlots(slotType, mLastUpdatedTime, currentTimeMillis); + if (offset < LONG_SIZE_BITS) { + mEventBitmaps[slotType] <<= offset; + } else { + mEventBitmaps[slotType] = 0L; + } + } + mLastUpdatedTime = currentTimeMillis; + } + + private static LocalDateTime toLocalDateTime(long epochMilli) { + return LocalDateTime.ofInstant( + Instant.ofEpochMilli(epochMilli), TimeZone.getDefault().toZoneId()); + } + + private static long toEpochMilli(LocalDateTime localDateTime) { + return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } + + private static long getDuration(Range<Long> timeSlot) { + return timeSlot.getUpper() - timeSlot.getLower(); + } + + /** + * Finds the time slots for the given two timestamps and returns the distance (in the number + * of time slots) between these two time slots. + */ + private static int diffTimeSlots(@TimeSlotType int timeSlotType, long fromTime, long toTime) { + Function<Long, Range<Long>> timeSlotFactory = TIME_SLOT_FACTORIES.get(timeSlotType); + Range<Long> fromSlot = timeSlotFactory.apply(fromTime); + Range<Long> toSlot = timeSlotFactory.apply(toTime); + return (int) ((toSlot.getLower() - fromSlot.getLower()) / getDuration(fromSlot)); + } + + /** + * Returns the active time slots for a specified type. The returned time slots are in + * reverse-chronological order. + */ + private List<Range<Long>> getActiveTimeSlotsForType(@TimeSlotType int timeSlotType) { + long eventBitmap = mEventBitmaps[timeSlotType]; + Range<Long> latestTimeSlot = TIME_SLOT_FACTORIES.get(timeSlotType).apply(mLastUpdatedTime); + long startTime = latestTimeSlot.getLower(); + final long duration = getDuration(latestTimeSlot); + List<Range<Long>> timeSlots = new ArrayList<>(); + while (eventBitmap != 0) { + int trailingZeros = Long.numberOfTrailingZeros(eventBitmap); + if (trailingZeros > 0) { + startTime -= duration * trailingZeros; + eventBitmap >>>= trailingZeros; + } + if (eventBitmap != 0) { + timeSlots.add(Range.create(startTime, startTime + duration)); + startTime -= duration; + eventBitmap >>>= 1; + } + } + return timeSlots; + } + + /** + * Combines two lists of time slots into one. If one longer time slot covers one or multiple + * shorter time slots, the smaller time slot(s) will be added to the result and the longer one + * will be dropped. This ensures the returned list does not contain any overlapping time slots. + */ + private static List<Range<Long>> combineTimeSlotLists(List<Range<Long>> longerSlots, + List<Range<Long>> shorterSlots) { + List<Range<Long>> result = new ArrayList<>(); + int i = 0; + int j = 0; + while (i < longerSlots.size() && j < shorterSlots.size()) { + Range<Long> longerSlot = longerSlots.get(i); + Range<Long> shorterSlot = shorterSlots.get(j); + if (longerSlot.contains(shorterSlot)) { + result.add(shorterSlot); + i++; + j++; + } else if (longerSlot.getLower() < shorterSlot.getLower()) { + result.add(shorterSlot); + j++; + } else { + result.add(longerSlot); + i++; + } + } + if (i < longerSlots.size()) { + result.addAll(longerSlots.subList(i, longerSlots.size())); + } else if (j < shorterSlots.size()) { + result.addAll(shorterSlots.subList(j, shorterSlots.size())); + } + return result; + } + + /** + * Finds and creates the time slot (duration = 1 day) that the given time falls into. + */ + @NonNull + private static Range<Long> createOneDayLongTimeSlot(long time) { + LocalDateTime beginTime = toLocalDateTime(time).truncatedTo(ChronoUnit.DAYS); + return Range.create(toEpochMilli(beginTime), toEpochMilli(beginTime.plusDays(1))); + } + + /** + * Finds and creates the time slot (duration = 4 hours) that the given time falls into. + */ + @NonNull + private static Range<Long> createFourHoursLongTimeSlot(long time) { + int hourOfDay = toLocalDateTime(time).getHour(); + LocalDateTime beginTime = + toLocalDateTime(time).truncatedTo(ChronoUnit.HOURS).minusHours(hourOfDay % 4); + return Range.create(toEpochMilli(beginTime), toEpochMilli(beginTime.plusHours(4))); + } + + /** + * Finds and creates the time slot (duration = 1 hour) that the given time falls into. + */ + @NonNull + private static Range<Long> createOneHourLongTimeSlot(long time) { + LocalDateTime beginTime = toLocalDateTime(time).truncatedTo(ChronoUnit.HOURS); + return Range.create(toEpochMilli(beginTime), toEpochMilli(beginTime.plusHours(1))); + } + + /** + * Finds and creates the time slot (duration = 2 minutes) that the given time falls into. + */ + @NonNull + private static Range<Long> createTwoMinutesLongTimeSlot(long time) { + int minuteOfHour = toLocalDateTime(time).getMinute(); + LocalDateTime beginTime = toLocalDateTime(time).truncatedTo( + ChronoUnit.MINUTES).minusMinutes(minuteOfHour % 2); + return Range.create(toEpochMilli(beginTime), toEpochMilli(beginTime.plusMinutes(2))); + } + + @VisibleForTesting + static class Injector { + /** This should be the only way to get the current timestamp in {@code EventIndex}. */ + long currentTimeMillis() { + return System.currentTimeMillis(); + } + } +} diff --git a/services/people/java/com/android/server/people/data/EventList.java b/services/people/java/com/android/server/people/data/EventList.java new file mode 100644 index 000000000000..b267d667b422 --- /dev/null +++ b/services/people/java/com/android/server/people/data/EventList.java @@ -0,0 +1,103 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** A container that holds a list of {@link Event}s in chronological order. */ +class EventList { + + private final List<Event> mEvents = new ArrayList<>(); + + /** + * Adds an event to the list unless there is an existing event with the same timestamp and + * type. + */ + void add(@NonNull Event event) { + int index = firstIndexOnOrAfter(event.getTimestamp()); + if (index < mEvents.size() + && mEvents.get(index).getTimestamp() == event.getTimestamp() + && isDuplicate(event, index)) { + return; + } + mEvents.add(index, event); + } + + /** + * Returns a {@link List} of {@link Event}s whose timestamps are between the specified {@code + * fromTimestamp}, inclusive, and {@code toTimestamp} exclusive, and match the specified event + * types. + * + * @return a {@link List} of matched {@link Event}s in chronological order. + */ + @NonNull + List<Event> queryEvents(@NonNull Set<Integer> eventTypes, long fromTimestamp, + long toTimestamp) { + int fromIndex = firstIndexOnOrAfter(fromTimestamp); + if (fromIndex == mEvents.size()) { + return new ArrayList<>(); + } + int toIndex = firstIndexOnOrAfter(toTimestamp); + if (toIndex < fromIndex) { + return new ArrayList<>(); + } + List<Event> result = new ArrayList<>(); + for (int i = fromIndex; i < toIndex; i++) { + Event e = mEvents.get(i); + if (eventTypes.contains(e.getType())) { + result.add(e); + } + } + return result; + } + + /** Returns the first index whose timestamp is greater or equal to the provided timestamp. */ + private int firstIndexOnOrAfter(long timestamp) { + int result = mEvents.size(); + int low = 0; + int high = mEvents.size() - 1; + while (low <= high) { + int mid = (low + high) >>> 1; + if (mEvents.get(mid).getTimestamp() >= timestamp) { + high = mid - 1; + result = mid; + } else { + low = mid + 1; + } + } + return result; + } + + /** + * Checks whether the {@link Event} is duplicate with one of the existing events. The checking + * starts from the {@code startIndex}. + */ + private boolean isDuplicate(Event event, int startIndex) { + int size = mEvents.size(); + int index = startIndex; + while (index < size && mEvents.get(index).getTimestamp() <= event.getTimestamp()) { + if (mEvents.get(index++).getType() == event.getType()) { + return true; + } + } + return false; + } +} diff --git a/services/people/java/com/android/server/people/data/EventStore.java b/services/people/java/com/android/server/people/data/EventStore.java new file mode 100644 index 000000000000..d6b7a863ca2d --- /dev/null +++ b/services/people/java/com/android/server/people/data/EventStore.java @@ -0,0 +1,114 @@ +/* + * 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.Nullable; +import android.content.LocusId; +import android.util.ArrayMap; + +import java.util.Map; + +/** The store that stores and accesses the events data for a package. */ +class EventStore { + + private final EventHistoryImpl mPackageEventHistory = new EventHistoryImpl(); + + // Shortcut ID -> Event History + private final Map<String, EventHistoryImpl> mShortcutEventHistoryMap = new ArrayMap<>(); + + // Locus ID -> Event History + private final Map<LocusId, EventHistoryImpl> mLocusEventHistoryMap = new ArrayMap<>(); + + // Phone Number -> Event History + private final Map<String, EventHistoryImpl> mCallEventHistoryMap = new ArrayMap<>(); + + // Phone Number -> Event History + private final Map<String, EventHistoryImpl> mSmsEventHistoryMap = new ArrayMap<>(); + + /** Gets the package level {@link EventHistory}. */ + @NonNull + EventHistory getPackageEventHistory() { + return mPackageEventHistory; + } + + /** Gets the {@link EventHistory} for the specified {@code shortcutId} if exists. */ + @Nullable + EventHistory getShortcutEventHistory(String shortcutId) { + return mShortcutEventHistoryMap.get(shortcutId); + } + + /** Gets the {@link EventHistory} for the specified {@code locusId} if exists. */ + @Nullable + EventHistory getLocusEventHistory(LocusId locusId) { + return mLocusEventHistoryMap.get(locusId); + } + + /** Gets the phone call {@link EventHistory} for the specified {@code phoneNumber} if exists. */ + @Nullable + EventHistory getCallEventHistory(String phoneNumber) { + return mCallEventHistoryMap.get(phoneNumber); + } + + /** Gets the SMS {@link EventHistory} for the specified {@code phoneNumber} if exists. */ + @Nullable + EventHistory getSmsEventHistory(String phoneNumber) { + return mSmsEventHistoryMap.get(phoneNumber); + } + + /** + * Gets the {@link EventHistoryImpl} for the specified {@code shortcutId} or creates a new + * instance and put it into the store if not exists. The caller needs to verify if a + * conversation with this shortcut ID exists before calling this method. + */ + @NonNull + EventHistoryImpl getOrCreateShortcutEventHistory(String shortcutId) { + return mShortcutEventHistoryMap.computeIfAbsent(shortcutId, key -> new EventHistoryImpl()); + } + + /** + * Gets the {@link EventHistoryImpl} for the specified {@code locusId} or creates a new + * instance and put it into the store if not exists. The caller needs to ensure a conversation + * with this locus ID exists before calling this method. + */ + @NonNull + EventHistoryImpl getOrCreateLocusEventHistory(LocusId locusId) { + return mLocusEventHistoryMap.computeIfAbsent(locusId, key -> new EventHistoryImpl()); + } + + /** + * Gets the {@link EventHistoryImpl} for the specified {@code phoneNumber} for call events + * or creates a new instance and put it into the store if not exists. The caller needs to ensure + * a conversation with this phone number exists and this package is the default dialer + * before calling this method. + */ + @NonNull + EventHistoryImpl getOrCreateCallEventHistory(String phoneNumber) { + return mCallEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl()); + } + + /** + * Gets the {@link EventHistoryImpl} for the specified {@code phoneNumber} for SMS events + * or creates a new instance and put it into the store if not exists. The caller needs to ensure + * a conversation with this phone number exists and this package is the default SMS app + * before calling this method. + */ + @NonNull + EventHistoryImpl getOrCreateSmsEventHistory(String phoneNumber) { + return mSmsEventHistoryMap.computeIfAbsent(phoneNumber, key -> new EventHistoryImpl()); + } +} diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java new file mode 100644 index 000000000000..9c22a7f1c484 --- /dev/null +++ b/services/people/java/com/android/server/people/data/PackageData.java @@ -0,0 +1,145 @@ +/* + * 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.content.LocusId; +import android.text.TextUtils; + +import java.util.function.Consumer; + +/** The data associated with a package. */ +public class PackageData { + + @NonNull + private final String mPackageName; + + private final @UserIdInt int mUserId; + + @NonNull + private final ConversationStore mConversationStore; + + @NonNull + private final EventStore mEventStore; + + private boolean mIsDefaultDialer; + + private boolean mIsDefaultSmsApp; + + PackageData(@NonNull String packageName, @UserIdInt int userId) { + mPackageName = packageName; + mUserId = userId; + mConversationStore = new ConversationStore(); + mEventStore = new EventStore(); + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + + public @UserIdInt int getUserId() { + return mUserId; + } + + /** Iterates over all the conversations in this package. */ + public void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { + mConversationStore.forAllConversations(consumer); + } + + @NonNull + public EventHistory getPackageLevelEventHistory() { + return getEventStore().getPackageEventHistory(); + } + + /** + * Gets the combined {@link EventHistory} for a given shortcut ID. This returned {@link + * EventHistory} has events of all types, no matter whether they're annotated with shortcut ID, + * Locus ID, or phone number etc. + */ + @NonNull + public EventHistory getEventHistory(@NonNull String shortcutId) { + AggregateEventHistoryImpl result = new AggregateEventHistoryImpl(); + + ConversationInfo conversationInfo = mConversationStore.getConversation(shortcutId); + if (conversationInfo == null) { + return result; + } + + EventHistory shortcutEventHistory = getEventStore().getShortcutEventHistory(shortcutId); + if (shortcutEventHistory != null) { + result.addEventHistory(shortcutEventHistory); + } + + LocusId locusId = conversationInfo.getLocusId(); + if (locusId != null) { + EventHistory locusEventHistory = getEventStore().getLocusEventHistory(locusId); + if (locusEventHistory != null) { + result.addEventHistory(locusEventHistory); + } + } + + String phoneNumber = conversationInfo.getContactPhoneNumber(); + if (TextUtils.isEmpty(phoneNumber)) { + return result; + } + if (isDefaultDialer()) { + EventHistory callEventHistory = getEventStore().getCallEventHistory(phoneNumber); + if (callEventHistory != null) { + result.addEventHistory(callEventHistory); + } + } + if (isDefaultSmsApp()) { + EventHistory smsEventHistory = getEventStore().getSmsEventHistory(phoneNumber); + if (smsEventHistory != null) { + result.addEventHistory(smsEventHistory); + } + } + return result; + } + + public boolean isDefaultDialer() { + return mIsDefaultDialer; + } + + public boolean isDefaultSmsApp() { + return mIsDefaultSmsApp; + } + + @NonNull + ConversationStore getConversationStore() { + return mConversationStore; + } + + @NonNull + EventStore getEventStore() { + return mEventStore; + } + + void setIsDefaultDialer(boolean value) { + mIsDefaultDialer = value; + } + + void setIsDefaultSmsApp(boolean value) { + mIsDefaultSmsApp = value; + } + + void onDestroy() { + // TODO: STOPSHIP: Implements this method for the case of package being uninstalled. + } +} diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java new file mode 100644 index 000000000000..2c16059e89ba --- /dev/null +++ b/services/people/java/com/android/server/people/data/UserData.java @@ -0,0 +1,103 @@ +/* + * 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.Nullable; +import android.annotation.UserIdInt; +import android.text.TextUtils; +import android.util.ArrayMap; + +import java.util.Map; +import java.util.function.Consumer; + +/** The data associated with a user profile. */ +class UserData { + + private final @UserIdInt int mUserId; + + private boolean mIsUnlocked; + + private Map<String, PackageData> mPackageDataMap = new ArrayMap<>(); + + UserData(@UserIdInt int userId) { + mUserId = userId; + } + + @UserIdInt int getUserId() { + return mUserId; + } + + void forAllPackages(@NonNull Consumer<PackageData> consumer) { + for (PackageData packageData : mPackageDataMap.values()) { + consumer.accept(packageData); + } + } + + void setUserUnlocked() { + mIsUnlocked = true; + } + + void setUserStopped() { + mIsUnlocked = false; + } + + boolean isUnlocked() { + return mIsUnlocked; + } + + /** + * Gets the {@link PackageData} for the specified {@code packageName} if exists; otherwise + * creates a new instance and returns it. + */ + @NonNull + PackageData getOrCreatePackageData(String packageName) { + return mPackageDataMap.computeIfAbsent( + packageName, key -> new PackageData(packageName, mUserId)); + } + + /** + * Gets the {@link PackageData} for the specified {@code packageName} if exists; otherwise + * returns {@code null}. + */ + @Nullable + PackageData getPackageData(@NonNull String packageName) { + return mPackageDataMap.get(packageName); + } + + void setDefaultDialer(@Nullable String packageName) { + for (PackageData packageData : mPackageDataMap.values()) { + if (packageData.isDefaultDialer()) { + packageData.setIsDefaultDialer(false); + } + if (TextUtils.equals(packageName, packageData.getPackageName())) { + packageData.setIsDefaultDialer(true); + } + } + } + + void setDefaultSmsApp(@Nullable String packageName) { + for (PackageData packageData : mPackageDataMap.values()) { + if (packageData.isDefaultSmsApp()) { + packageData.setIsDefaultSmsApp(false); + } + if (TextUtils.equals(packageName, packageData.getPackageName())) { + packageData.setIsDefaultSmsApp(true); + } + } + } +} diff --git a/services/people/java/com/android/server/people/prediction/ConversationData.java b/services/people/java/com/android/server/people/prediction/ConversationData.java new file mode 100644 index 000000000000..0cc763329764 --- /dev/null +++ b/services/people/java/com/android/server/people/prediction/ConversationData.java @@ -0,0 +1,55 @@ +/* + * 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.prediction; + +import android.annotation.NonNull; + +import com.android.server.people.data.ConversationInfo; +import com.android.server.people.data.EventHistory; + +/** The conversation data which is used for scoring and then ranking the conversations. */ +class ConversationData { + + private final String mPackageName; + private final int mUserId; + private final ConversationInfo mConversationInfo; + private final EventHistory mEventHistory; + + ConversationData(@NonNull String packageName, int userId, + @NonNull ConversationInfo conversationInfo, @NonNull EventHistory eventHistory) { + mPackageName = packageName; + mUserId = userId; + mConversationInfo = conversationInfo; + mEventHistory = eventHistory; + } + + String getPackageName() { + return mPackageName; + } + + int getUserId() { + return mUserId; + } + + ConversationInfo getConversationInfo() { + return mConversationInfo; + } + + EventHistory getEventHistory() { + return mEventHistory; + } +} diff --git a/services/people/java/com/android/server/people/prediction/ConversationPredictor.java b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java index de71d292ff7d..ed8a56bb6435 100644 --- a/services/people/java/com/android/server/people/prediction/ConversationPredictor.java +++ b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java @@ -17,12 +17,20 @@ package com.android.server.people.prediction; import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; +import android.content.IntentFilter; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager.ShareShortcutInfo; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.ChooserActivity; +import com.android.server.people.data.DataManager; +import com.android.server.people.data.EventHistory; import java.util.ArrayList; import java.util.List; @@ -35,15 +43,28 @@ import java.util.function.Consumer; */ public class ConversationPredictor { + private static final String UI_SURFACE_SHARE = "share"; + private final AppPredictionContext mPredictionContext; private final Consumer<List<AppTarget>> mUpdatePredictionsMethod; + private final DataManager mDataManager; private final ExecutorService mCallbackExecutor; + @Nullable + private final IntentFilter mIntentFilter; - public ConversationPredictor(AppPredictionContext predictionContext, - Consumer<List<AppTarget>> updatePredictionsMethod) { + public ConversationPredictor(@NonNull AppPredictionContext predictionContext, + @NonNull Consumer<List<AppTarget>> updatePredictionsMethod, + @NonNull DataManager dataManager) { mPredictionContext = predictionContext; mUpdatePredictionsMethod = updatePredictionsMethod; + mDataManager = dataManager; mCallbackExecutor = Executors.newSingleThreadExecutor(); + if (UI_SURFACE_SHARE.equals(mPredictionContext.getUiSurface())) { + mIntentFilter = mPredictionContext.getExtras().getParcelable( + ChooserActivity.APP_PREDICTION_INTENT_FILTER_KEY); + } else { + mIntentFilter = null; + } } /** @@ -51,14 +72,14 @@ public class ConversationPredictor { */ @MainThread public void onAppTargetEvent(AppTargetEvent event) { + mDataManager.reportAppTargetEvent(event, mIntentFilter); } /** * Called by the client app to indicate a particular location has been shown to the user. */ @MainThread - public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) { - } + public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) {} /** * Called by the client app to request sorting of the provided targets based on the prediction @@ -74,8 +95,44 @@ public class ConversationPredictor { */ @MainThread public void onRequestPredictionUpdate() { - List<AppTarget> targets = new ArrayList<>(); - mCallbackExecutor.execute(() -> mUpdatePredictionsMethod.accept(targets)); + // TODO: Re-route the call to different ranking classes for different surfaces. + mCallbackExecutor.execute(() -> { + List<AppTarget> targets = new ArrayList<>(); + if (mIntentFilter != null) { + List<ShareShortcutInfo> shareShortcuts = + mDataManager.getConversationShareTargets(mIntentFilter); + for (ShareShortcutInfo shareShortcut : shareShortcuts) { + ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo(); + AppTargetId appTargetId = new AppTargetId(shortcutInfo.getId()); + String shareTargetClass = shareShortcut.getTargetComponent().getClassName(); + targets.add(new AppTarget.Builder(appTargetId, shortcutInfo) + .setClassName(shareTargetClass) + .build()); + } + } else { + List<ConversationData> conversationDataList = new ArrayList<>(); + mDataManager.forAllPackages(packageData -> + packageData.forAllConversations(conversationInfo -> { + EventHistory eventHistory = packageData.getEventHistory( + conversationInfo.getShortcutId()); + ConversationData conversationData = new ConversationData( + packageData.getPackageName(), packageData.getUserId(), + conversationInfo, eventHistory); + conversationDataList.add(conversationData); + })); + for (ConversationData conversationData : conversationDataList) { + String shortcutId = conversationData.getConversationInfo().getShortcutId(); + ShortcutInfo shortcut = mDataManager.getShortcut( + conversationData.getPackageName(), conversationData.getUserId(), + shortcutId); + if (shortcut != null) { + AppTargetId appTargetId = new AppTargetId(shortcut.getId()); + targets.add(new AppTarget.Builder(appTargetId, shortcut).build()); + } + } + } + mUpdatePredictionsMethod.accept(targets); + }); } @VisibleForTesting diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 2080fdf2e40d..c94bb879f460 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -375,6 +375,35 @@ public class RescuePartyTest { } @Test + public void testBootLoopLevels() { + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + + /* + Ensure that the returned user impact corresponds with the user impact of the next available + rescue level, not the current one. + */ + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_NONE)); + assertEquals(observer.onBootLoop(), PackageHealthObserverImpact.USER_IMPACT_LOW); + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS)); + assertEquals(observer.onBootLoop(), PackageHealthObserverImpact.USER_IMPACT_LOW); + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES)); + assertEquals(observer.onBootLoop(), PackageHealthObserverImpact.USER_IMPACT_HIGH); + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS)); + assertEquals(observer.onBootLoop(), PackageHealthObserverImpact.USER_IMPACT_HIGH); + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + LEVEL_FACTORY_RESET)); + assertEquals(observer.onBootLoop(), PackageHealthObserverImpact.USER_IMPACT_HIGH); + } + + @Test public void testRescueLevelIncrementsWhenExecuted() { RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index d2ddff3627b9..b7c900130e39 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -66,6 +66,8 @@ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.SUSPEND_APPS"/> <uses-permission android:name="android.permission.CONTROL_KEYGUARD"/> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> <uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE"/> <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java index 6359edf190b0..3d220432cc8e 100644 --- a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java @@ -20,7 +20,6 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; @@ -55,6 +54,8 @@ public class PerformUnifiedRestoreTaskTest { private static final String INCLUDED_KEY = "included_key"; private static final String EXCLUDED_KEY_1 = "excluded_key_1"; private static final String EXCLUDED_KEY_2 = "excluded_key_2"; + private static final String SYSTEM_PACKAGE_NAME = "android"; + private static final String NON_SYSTEM_PACKAGE_NAME = "package"; @Mock private BackupDataInput mBackupDataInput; @Mock private BackupDataOutput mBackupDataOutput; @@ -98,9 +99,6 @@ public class PerformUnifiedRestoreTaskTest { return null; } }); - - mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( - PACKAGE_NAME, mExcludedkeys)); } private void populateTestData() { @@ -116,6 +114,8 @@ public class PerformUnifiedRestoreTaskTest { @Test public void testFilterExcludedKeys() throws Exception { + mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( + PACKAGE_NAME, mExcludedkeys)); mRestoreTask.filterExcludedKeys(PACKAGE_NAME, mBackupDataInput, mBackupDataOutput); // Verify only the correct were written into BackupDataOutput object. @@ -123,4 +123,34 @@ public class PerformUnifiedRestoreTaskTest { allowedBackupKeys.removeAll(mExcludedkeys); assertEquals(allowedBackupKeys, mBackupDataDump); } + + @Test + public void testStageBackupData_stageForNonSystemPackageWithKeysToExclude() { + mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( + PACKAGE_NAME, mExcludedkeys)); + + assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); + } + + @Test + public void testStageBackupData_stageForNonSystemPackageWithNoKeysToExclude() { + mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap()); + + assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); + } + + @Test + public void testStageBackupData_doNotStageForSystemPackageWithNoKeysToExclude() { + mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap()); + + assertFalse(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME)); + } + + @Test + public void testStageBackupData_stageForSystemPackageWithKeysToExclude() { + mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( + PACKAGE_NAME, mExcludedkeys)); + + assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); + } } 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 def5b617becd..422c278a3af7 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -1986,7 +1986,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { UserManager.DISALLOW_SAFE_BOOT, UserManager.DISALLOW_SHARE_LOCATION, UserManager.DISALLOW_SMS, - UserManager.DISALLOW_USB_FILE_TRANSFER + UserManager.DISALLOW_USB_FILE_TRANSFER, + UserManager.DISALLOW_AIRPLANE_MODE, + UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, + UserManager.DISALLOW_OUTGOING_CALLS, + UserManager.DISALLOW_UNMUTE_MICROPHONE ); public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception { 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 770afb0a24d5..49ad866a56a4 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -328,10 +328,11 @@ public class AppIntegrityManagerServiceImplTest { when(mRuleEvaluationEngine.evaluate(any(), any())) .thenReturn( IntegrityCheckResult.deny( - new Rule( - new AtomicFormula.BooleanAtomicFormula( - AtomicFormula.PRE_INSTALLED, false), - Rule.DENY))); + Arrays.asList( + new Rule( + new AtomicFormula.BooleanAtomicFormula( + AtomicFormula.PRE_INSTALLED, false), + Rule.DENY)))); Intent intent = makeVerificationIntent(); broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); 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 629fd14befcc..7b53e5e7c762 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 @@ -63,7 +63,7 @@ public class RuleEvaluatorTest { @Test public void testEvaluateRules_noMatchedRules_allow() { - Rule rule1 = + Rule rule = new Rule( new StringAtomicFormula( AtomicFormula.PACKAGE_NAME, @@ -72,7 +72,7 @@ public class RuleEvaluatorTest { Rule.DENY); IntegrityCheckResult result = - RuleEvaluator.evaluateRules(Collections.singletonList(rule1), APP_INSTALL_METADATA); + RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); assertThat(result.getEffect()).isEqualTo(ALLOW); } @@ -98,7 +98,7 @@ public class RuleEvaluatorTest { RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), APP_INSTALL_METADATA); assertThat(result.getEffect()).isEqualTo(DENY); - assertThat(result.getRule()).isEqualTo(rule1); + assertThat(result.getMatchedRules()).containsExactly(rule1); } @Test @@ -110,7 +110,7 @@ public class RuleEvaluatorTest { PACKAGE_NAME_1, /* isHashedValue= */ false), Rule.DENY); - CompoundFormula compoundFormula2 = + Rule rule2 = new Rule( new CompoundFormula( CompoundFormula.AND, Arrays.asList( @@ -121,33 +121,33 @@ public class RuleEvaluatorTest { new StringAtomicFormula( AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE, - /* isHashedValue= */ false))); - Rule rule2 = new Rule(compoundFormula2, Rule.DENY); + /* isHashedValue= */ false))), + Rule.DENY); IntegrityCheckResult result = RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), APP_INSTALL_METADATA); assertThat(result.getEffect()).isEqualTo(DENY); - assertThat(result.getRule()).isEqualTo(rule1); + assertThat(result.getMatchedRules()).containsExactly(rule1, rule2); } @Test public void testEvaluateRules_ruleWithNot_deny() { - CompoundFormula compoundFormula = + Rule rule = new Rule( new CompoundFormula( CompoundFormula.NOT, Collections.singletonList( new StringAtomicFormula( AtomicFormula.PACKAGE_NAME, PACKAGE_NAME_2, - /* isHashedValue= */ false))); - Rule rule = new Rule(compoundFormula, Rule.DENY); + /* isHashedValue= */ false))), + Rule.DENY); IntegrityCheckResult result = RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); assertThat(result.getEffect()).isEqualTo(DENY); - assertThat(result.getRule()).isEqualTo(rule); + assertThat(result.getMatchedRules()).containsExactly(rule); } @Test @@ -162,12 +162,12 @@ public class RuleEvaluatorTest { RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); assertThat(result.getEffect()).isEqualTo(DENY); - assertThat(result.getRule()).isEqualTo(rule); + assertThat(result.getMatchedRules()).containsExactly(rule); } @Test public void testEvaluateRules_validForm_deny() { - CompoundFormula compoundFormula = + Rule rule = new Rule( new CompoundFormula( CompoundFormula.AND, Arrays.asList( @@ -178,19 +178,19 @@ public class RuleEvaluatorTest { new StringAtomicFormula( AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE, - /* isHashedValue= */ false))); - Rule rule = new Rule(compoundFormula, Rule.DENY); + /* isHashedValue= */ false))), + Rule.DENY); IntegrityCheckResult result = RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); assertThat(result.getEffect()).isEqualTo(DENY); - assertThat(result.getRule()).isEqualTo(rule); + assertThat(result.getMatchedRules()).containsExactly(rule); } @Test public void testEvaluateRules_orRules() { - CompoundFormula compoundFormula = + Rule rule = new Rule( new CompoundFormula( CompoundFormula.OR, Arrays.asList( @@ -201,13 +201,14 @@ public class RuleEvaluatorTest { new StringAtomicFormula( AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE, - /* isHashedValue= */ false))); - Rule rule = new Rule(compoundFormula, Rule.DENY); + /* isHashedValue= */ false))), + Rule.DENY); IntegrityCheckResult result = RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); assertThat(result.getEffect()).isEqualTo(DENY); + assertThat(result.getMatchedRules()).containsExactly(rule); } @Test @@ -232,6 +233,7 @@ public class RuleEvaluatorTest { RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA); assertThat(result.getEffect()).isEqualTo(DENY); + assertThat(result.getMatchedRules()).containsExactly(rule); } @Test @@ -243,7 +245,7 @@ public class RuleEvaluatorTest { PACKAGE_NAME_1, /* isHashedValue= */ false), Rule.FORCE_ALLOW); - CompoundFormula compoundFormula2 = + Rule rule2 = new Rule( new CompoundFormula( CompoundFormula.AND, Arrays.asList( @@ -254,13 +256,43 @@ public class RuleEvaluatorTest { new StringAtomicFormula( AtomicFormula.APP_CERTIFICATE, APP_CERTIFICATE, - /* isHashedValue= */ false))); - Rule rule2 = new Rule(compoundFormula2, Rule.DENY); + /* isHashedValue= */ false))), + Rule.DENY); + + IntegrityCheckResult result = + RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), APP_INSTALL_METADATA); + + assertThat(result.getEffect()).isEqualTo(ALLOW); + assertThat(result.getMatchedRules()).containsExactly(rule1); + } + + @Test + public void testEvaluateRules_multipleMatches_forceAllow() { + Rule rule1 = + new Rule( + new StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, + PACKAGE_NAME_1, + /* isHashedValue= */ false), + Rule.FORCE_ALLOW); + Rule rule2 = new Rule( + new CompoundFormula( + CompoundFormula.AND, + Arrays.asList( + new StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, + PACKAGE_NAME_1, + /* isHashedValue= */ false), + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, + APP_CERTIFICATE, + /* isHashedValue= */ false))), + Rule.FORCE_ALLOW); IntegrityCheckResult result = RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2), APP_INSTALL_METADATA); assertThat(result.getEffect()).isEqualTo(ALLOW); - assertThat(result.getRule()).isEqualTo(rule1); + assertThat(result.getMatchedRules()).containsExactly(rule1, rule2); } }
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java new file mode 100644 index 000000000000..ec1423958f8f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java @@ -0,0 +1,135 @@ +/* + * 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.integrity.model; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.integrity.AtomicFormula; +import android.content.integrity.CompoundFormula; +import android.content.integrity.Rule; +import android.util.StatsLog; + +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 IntegrityCheckResultTest { + + @Test + public void createAllowResult() { + IntegrityCheckResult allowResult = IntegrityCheckResult.allow(); + + assertThat(allowResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.ALLOW); + assertThat(allowResult.getMatchedRules()).isEmpty(); + assertThat(allowResult.getLoggingResponse()) + .isEqualTo(StatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__ALLOWED); + } + + @Test + public void createAllowResultWithRule() { + String packageName = "com.test.deny"; + Rule forceAllowRule = + new Rule( + new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, + packageName), + Rule.FORCE_ALLOW); + + IntegrityCheckResult allowResult = + IntegrityCheckResult.allow(Collections.singletonList(forceAllowRule)); + + assertThat(allowResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.ALLOW); + assertThat(allowResult.getMatchedRules()).containsExactly(forceAllowRule); + assertThat(allowResult.getLoggingResponse()) + .isEqualTo(StatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__FORCE_ALLOWED); + } + + @Test + public void createDenyResultWithRule() { + String packageName = "com.test.deny"; + Rule failedRule = + new Rule( + new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, + packageName), + Rule.DENY); + + IntegrityCheckResult denyResult = + IntegrityCheckResult.deny(Collections.singletonList(failedRule)); + + assertThat(denyResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.DENY); + assertThat(denyResult.getMatchedRules()).containsExactly(failedRule); + assertThat(denyResult.getLoggingResponse()) + .isEqualTo(StatsLog.INTEGRITY_CHECK_RESULT_REPORTED__RESPONSE__REJECTED); + } + + @Test + public void isDenyCausedByAppCertificate() { + String packageName = "com.test.deny"; + String appCert = "app-cert"; + Rule failedRule = + new Rule( + new CompoundFormula( + CompoundFormula.AND, + Arrays.asList( + new AtomicFormula.StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, packageName), + new AtomicFormula.StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, appCert))), + Rule.DENY); + Rule otherFailedRule = + new Rule( + new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE, + AtomicFormula.EQ, 12), + Rule.DENY); + + IntegrityCheckResult denyResult = + IntegrityCheckResult.deny(Arrays.asList(failedRule, otherFailedRule)); + + assertThat(denyResult.isCausedByAppCertRule()).isTrue(); + assertThat(denyResult.isCausedByInstallerRule()).isFalse(); + } + + @Test + public void isDenyCausedByInstaller() { + String packageName = "com.test.deny"; + String appCert = "app-cert"; + Rule failedRule = + new Rule( + new CompoundFormula( + CompoundFormula.AND, + Arrays.asList( + new AtomicFormula.StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, packageName), + new AtomicFormula.StringAtomicFormula( + AtomicFormula.INSTALLER_CERTIFICATE, appCert))), + Rule.DENY); + Rule otherFailedRule = + new Rule( + new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE, + AtomicFormula.EQ, 12), + Rule.DENY); + + IntegrityCheckResult denyResult = + IntegrityCheckResult.deny(Arrays.asList(failedRule, otherFailedRule)); + + assertThat(denyResult.isCausedByAppCertRule()).isFalse(); + assertThat(denyResult.isCausedByInstallerRule()).isTrue(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java deleted file mode 100644 index c57136c1acab..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java +++ /dev/null @@ -1,640 +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.server.integrity.parser; - -import static com.android.server.testutils.TestUtils.assertExpectException; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.Rule; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@RunWith(JUnit4.class) -public class RuleXmlParserTest { - - @Test - public void testXmlStream_validCompoundFormula() throws Exception { - Map<String, String> atomicFormulaAttrs = new HashMap<>(); - atomicFormulaAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - atomicFormulaAttrs.put("V", "com.app.test"); - String ruleXmlCompoundFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", atomicFormulaAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false))), - Rule.DENY); - - List<Rule> rules = xmlParser.parse(ruleXmlCompoundFormula.getBytes()); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testXmlString_validCompoundFormula_notConnector() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlCompoundFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false))), - Rule.DENY); - - List<Rule> rules = xmlParser.parse(ruleXmlCompoundFormula.getBytes(StandardCharsets.UTF_8)); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testXmlString_validCompoundFormula_andConnector() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - Map<String, String> appCertificateAttrs = new HashMap<>(); - appCertificateAttrs.put("K", String.valueOf(AtomicFormula.APP_CERTIFICATE)); - appCertificateAttrs.put("V", "test_cert"); - String ruleXmlCompoundFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.AND)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + generateTagWithAttribute( - /* tag= */ "AF", appCertificateAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - "test_cert", - /* isHashedValue= */ false))), - Rule.DENY); - List<Rule> rules = xmlParser.parse(ruleXmlCompoundFormula.getBytes(StandardCharsets.UTF_8)); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testXmlString_validCompoundFormula_orConnector() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - Map<String, String> appCertificateAttrs = new HashMap<>(); - appCertificateAttrs.put("K", String.valueOf(AtomicFormula.APP_CERTIFICATE)); - appCertificateAttrs.put("V", "test_cert"); - String ruleXmlCompoundFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.OR)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + generateTagWithAttribute( - /* tag= */ "AF", appCertificateAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.OR, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - "test_cert", - /* isHashedValue= */ false))), - Rule.DENY); - - List<Rule> rules = xmlParser.parse(ruleXmlCompoundFormula.getBytes(StandardCharsets.UTF_8)); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testXmlString_validCompoundFormula_differentTagOrder() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlCompoundFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - Rule expectedRule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false))), - Rule.DENY); - - List<Rule> rules = xmlParser.parse(ruleXmlCompoundFormula.getBytes(StandardCharsets.UTF_8)); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testXmlString_invalidCompoundFormula_invalidNumberOfFormulas() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - Map<String, String> versionCodeAttrs = new HashMap<>(); - versionCodeAttrs.put("K", String.valueOf(AtomicFormula.VERSION_CODE)); - versionCodeAttrs.put("O", String.valueOf(AtomicFormula.EQ)); - versionCodeAttrs.put("V", "1"); - String ruleXmlCompoundFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + generateTagWithAttribute( - /* tag= */ "AF", versionCodeAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "Connector NOT must have 1 formula only", - () -> xmlParser.parse(ruleXmlCompoundFormula.getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void testXmlString_invalidCompoundFormula_invalidOperator() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("O", "INVALID_OPERATOR"); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlCompoundFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "For input string: \"INVALID_OPERATOR\"", - () -> xmlParser.parse(ruleXmlCompoundFormula.getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void testXmlString_invalidCompoundFormula_invalidEffect() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlCompoundFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", "INVALID_EFFECT"), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "For input string: \"INVALID_EFFECT\"", - () -> xmlParser.parse(ruleXmlCompoundFormula.getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void testXmlString_invalidCompoundFormula_invalidTags() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlCompoundFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "InvalidAtomicFormula", - packageNameAttrs, - /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "Found unexpected tag: InvalidAtomicFormula", - () -> xmlParser.parse(ruleXmlCompoundFormula.getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void testXmlString_validAtomicFormula_stringValue() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlAtomicFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - Rule expectedRule = - new Rule( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false), - Rule.DENY); - - List<Rule> rules = xmlParser.parse(ruleXmlAtomicFormula.getBytes(StandardCharsets.UTF_8)); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testXmlString_validAtomicFormula_integerValue() throws Exception { - Map<String, String> versionCodeAttrs = new HashMap<>(); - versionCodeAttrs.put("K", String.valueOf(AtomicFormula.VERSION_CODE)); - versionCodeAttrs.put("O", String.valueOf(AtomicFormula.EQ)); - versionCodeAttrs.put("V", "1"); - String ruleXmlAtomicFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", versionCodeAttrs, /* closed= */ true) - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - Rule expectedRule = - new Rule( - new AtomicFormula.LongAtomicFormula( - AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 1), - Rule.DENY); - - List<Rule> rules = xmlParser.parse(ruleXmlAtomicFormula.getBytes(StandardCharsets.UTF_8)); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testXmlString_validAtomicFormula_booleanValue() throws Exception { - Map<String, String> preInstalledAttrs = new HashMap<>(); - preInstalledAttrs.put("K", String.valueOf(AtomicFormula.PRE_INSTALLED)); - preInstalledAttrs.put("V", "true"); - String ruleXmlAtomicFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", preInstalledAttrs, /* closed= */ true) - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - Rule expectedRule = - new Rule( - new AtomicFormula.BooleanAtomicFormula( - AtomicFormula.PRE_INSTALLED, true), - Rule.DENY); - - List<Rule> rules = xmlParser.parse(ruleXmlAtomicFormula.getBytes(StandardCharsets.UTF_8)); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testXmlString_validAtomicFormula_differentAttributeOrder() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlAtomicFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - Rule expectedRule = - new Rule( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false), - Rule.DENY); - - List<Rule> rules = xmlParser.parse(ruleXmlAtomicFormula.getBytes(StandardCharsets.UTF_8)); - - assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); - } - - @Test - public void testXmlString_invalidAtomicFormula_invalidAttribute() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("BadKey", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlAtomicFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "Found unexpected key: -1", - () -> xmlParser.parse(ruleXmlAtomicFormula.getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void testXmlString_invalidRule_invalidAttribute() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlAtomicFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("BadEffect", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "Unknown effect: -1", - () -> xmlParser.parse(ruleXmlAtomicFormula.getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void testXmlString_invalidCompoundFormula_invalidAttribute() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlCompoundFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap( - "BadConnector", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "Unknown connector: -1", - () -> xmlParser.parse(ruleXmlCompoundFormula.getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void testXmlString_invalidAtomicFormula() throws Exception { - Map<String, String> packageNameAttrs = new HashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.VERSION_CODE)); - packageNameAttrs.put("O", String.valueOf(AtomicFormula.EQ)); - packageNameAttrs.put("V", "com.app.test"); - String ruleXmlAtomicFormula = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</R>" - + "</RL>"; - RuleParser xmlParser = new RuleXmlParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "For input string: \"com.app.test\"", - () -> xmlParser.parse(ruleXmlAtomicFormula.getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void testXmlString_withNoRuleList() { - Map<String, String> atomicFormulaAttrs = new HashMap<>(); - atomicFormulaAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - atomicFormulaAttrs.put("V", "com.app.test"); - String ruleXmlWithNoRuleList = - generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", atomicFormulaAttrs, /* closed= */ true) - + "</OF>" - + "</R>"; - RuleParser xmlParser = new RuleXmlParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag", - () -> xmlParser.parse(ruleXmlWithNoRuleList.getBytes(StandardCharsets.UTF_8))); - } - - @Test - public void testXmlStream_withNoRuleList() { - Map<String, String> atomicFormulaAttrs = new HashMap<>(); - atomicFormulaAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - atomicFormulaAttrs.put("V", "com.app.test"); - String ruleXmlWithNoRuleList = - generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", atomicFormulaAttrs, /* closed= */ true) - + "</OF>" - + "</R>"; - RuleParser xmlParser = new RuleXmlParser(); - - assertExpectException( - RuleParseException.class, - /* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag", - () -> xmlParser.parse(ruleXmlWithNoRuleList.getBytes())); - } - - private String generateTagWithAttribute( - String tag, Map<String, String> attributeValues, boolean closed) { - StringBuilder res = new StringBuilder("<"); - res.append(tag); - for (String attribute : attributeValues.keySet()) { - res.append(" "); - res.append(attribute); - res.append("=\""); - res.append(attributeValues.get(attribute)); - res.append("\""); - } - res.append(closed ? " />" : ">"); - return res.toString(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java index f3da286585fd..b3d949395d5c 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java @@ -872,6 +872,16 @@ public class RuleBinarySerializerTest { } @Override + public boolean isAppCertificateFormula() { + return false; + } + + @Override + public boolean isInstallerFormula() { + return false; + } + + @Override public int hashCode() { return super.hashCode(); } diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java index 038ab7ff0c35..913aff7daaa9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java @@ -299,6 +299,16 @@ public class RuleIndexingDetailsIdentifierTest { } @Override + public boolean isAppCertificateFormula() { + return false; + } + + @Override + public boolean isInstallerFormula() { + return false; + } + + @Override public int hashCode() { return super.hashCode(); } diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java deleted file mode 100644 index 6558cd538638..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java +++ /dev/null @@ -1,572 +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.server.integrity.serializer; - -import static com.android.server.testutils.TestUtils.assertExpectException; - -import static org.junit.Assert.assertEquals; - -import android.content.integrity.AppInstallMetadata; -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.IntegrityFormula; -import android.content.integrity.Rule; - -import androidx.annotation.NonNull; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.ByteArrayOutputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; - -@RunWith(JUnit4.class) -public class RuleXmlSerializerTest { - - private static final String SAMPLE_INSTALLER_NAME = "com.test.installer"; - private static final String SAMPLE_INSTALLER_CERT = "installer_cert"; - - @Test - public void testXmlString_serializeEmptyRuleList() throws Exception { - RuleSerializer xmlSerializer = new RuleXmlSerializer(); - String expectedRules = "<RL />"; - - byte[] actualRules = - xmlSerializer.serialize( - Collections.emptyList(), /* formatVersion= */ Optional.empty()); - - assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8)); - } - - @Test - public void testXmlString_serializeMultipleRules_indexingOrderPreserved() throws Exception { - String packageNameA = "aaa"; - String packageNameB = "bbb"; - String packageNameC = "ccc"; - String appCert1 = "cert1"; - String appCert2 = "cert2"; - String appCert3 = "cert3"; - Rule installerRule = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - SAMPLE_INSTALLER_NAME, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_CERTIFICATE, - SAMPLE_INSTALLER_CERT, - /* isHashedValue= */ false))), - Rule.DENY); - - RuleSerializer xmlSerializer = new RuleXmlSerializer(); - byte[] actualRules = - xmlSerializer.serialize( - Arrays.asList( - installerRule, - getRuleWithAppCertificateAndSampleInstallerName(appCert1), - getRuleWithPackageNameAndSampleInstallerName(packageNameB), - getRuleWithAppCertificateAndSampleInstallerName(appCert3), - getRuleWithPackageNameAndSampleInstallerName(packageNameC), - getRuleWithAppCertificateAndSampleInstallerName(appCert2), - getRuleWithPackageNameAndSampleInstallerName(packageNameA)), - /* formatVersion= */ Optional.empty()); - - String expectedRules = "<RL>" - + getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(packageNameA) - + getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(packageNameB) - + getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(packageNameC) - + getSerializedCompoundRuleWithAppCertificateAndSampleInstallerName(appCert1) - + getSerializedCompoundRuleWithAppCertificateAndSampleInstallerName(appCert2) - + getSerializedCompoundRuleWithAppCertificateAndSampleInstallerName(appCert3) - + getSerializedCompoundRuleWithSampleInstallerNameAndCert() - + "</RL>"; - - assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8)); - } - - @Test - public void testXmlStream_serializeValidCompoundFormula() throws Exception { - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false))), - Rule.DENY); - RuleSerializer xmlSerializer = new RuleXmlSerializer(); - OutputStream outputStream = new ByteArrayOutputStream(); - Map<String, String> packageNameAttrs = new LinkedHashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - packageNameAttrs.put("H", "false"); - String expectedRules = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - - xmlSerializer.serialize( - Collections.singletonList(rule), - /* formatVersion= */ Optional.empty(), - outputStream, - new ByteArrayOutputStream()); - - byte[] actualRules = outputStream.toString().getBytes(StandardCharsets.UTF_8); - assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8)); - } - - @Test - public void testXmlString_serializeValidCompoundFormula_notConnector() throws Exception { - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.NOT, - Collections.singletonList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false))), - Rule.DENY); - RuleSerializer xmlSerializer = new RuleXmlSerializer(); - Map<String, String> packageNameAttrs = new LinkedHashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - packageNameAttrs.put("H", "false"); - String expectedRules = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.NOT)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - - byte[] actualRules = - xmlSerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8)); - } - - @Test - public void testXmlString_serializeValidCompoundFormula_andConnector() throws Exception { - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - "test_cert", - /* isHashedValue= */ false))), - Rule.DENY); - RuleSerializer xmlSerializer = new RuleXmlSerializer(); - Map<String, String> packageNameAttrs = new LinkedHashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - packageNameAttrs.put("H", "false"); - Map<String, String> appCertificateAttrs = new LinkedHashMap<>(); - appCertificateAttrs.put("K", String.valueOf(AtomicFormula.APP_CERTIFICATE)); - appCertificateAttrs.put("V", "test_cert"); - appCertificateAttrs.put("H", "false"); - String expectedRules = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.AND)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + generateTagWithAttribute( - /* tag= */ "AF", appCertificateAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - - byte[] actualRules = - xmlSerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8)); - } - - @Test - public void testXmlString_serializeValidCompoundFormula_orConnector() throws Exception { - Rule rule = - new Rule( - new CompoundFormula( - CompoundFormula.OR, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - "test_cert", - /* isHashedValue= */ false))), - Rule.DENY); - RuleSerializer xmlSerializer = new RuleXmlSerializer(); - Map<String, String> packageNameAttrs = new LinkedHashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - packageNameAttrs.put("H", "false"); - Map<String, String> appCertificateAttrs = new LinkedHashMap<>(); - appCertificateAttrs.put("K", String.valueOf(AtomicFormula.APP_CERTIFICATE)); - appCertificateAttrs.put("V", "test_cert"); - appCertificateAttrs.put("H", "false"); - String expectedRules = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.OR)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + generateTagWithAttribute( - /* tag= */ "AF", appCertificateAttrs, /* closed= */ true) - + "</OF>" - + "</R>" - + "</RL>"; - - byte[] actualRules = - xmlSerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8)); - } - - @Test - public void testXmlString_serializeValidAtomicFormula_stringValue() throws Exception { - Rule rule = - new Rule( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - "com.app.test", - /* isHashedValue= */ false), - Rule.DENY); - RuleSerializer xmlSerializer = new RuleXmlSerializer(); - Map<String, String> packageNameAttrs = new LinkedHashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", "com.app.test"); - packageNameAttrs.put("H", "false"); - String expectedRules = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + "</R>" - + "</RL>"; - - byte[] actualRules = - xmlSerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8)); - } - - @Test - public void testXmlString_serializeValidAtomicFormula_integerValue() throws Exception { - Rule rule = - new Rule( - new AtomicFormula.LongAtomicFormula( - AtomicFormula.VERSION_CODE, AtomicFormula.EQ, 1), - Rule.DENY); - RuleSerializer xmlSerializer = new RuleXmlSerializer(); - Map<String, String> versionCodeAttrs = new LinkedHashMap<>(); - versionCodeAttrs.put("K", String.valueOf(AtomicFormula.VERSION_CODE)); - versionCodeAttrs.put("O", String.valueOf(AtomicFormula.EQ)); - versionCodeAttrs.put("V", "1"); - String expectedRules = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", versionCodeAttrs, /* closed= */ true) - + "</R>" - + "</RL>"; - - byte[] actualRules = - xmlSerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8)); - } - - @Test - public void testXmlString_serializeValidAtomicFormula_booleanValue() throws Exception { - Rule rule = - new Rule( - new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true), - Rule.DENY); - RuleSerializer xmlSerializer = new RuleXmlSerializer(); - Map<String, String> preInstalledAttrs = new LinkedHashMap<>(); - preInstalledAttrs.put("K", String.valueOf(AtomicFormula.PRE_INSTALLED)); - preInstalledAttrs.put("V", "true"); - String expectedRules = - "<RL>" - + generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", preInstalledAttrs, /* closed= */ true) - + "</R>" - + "</RL>"; - - byte[] actualRules = - xmlSerializer.serialize( - Collections.singletonList(rule), /* formatVersion= */ Optional.empty()); - - assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8)); - } - - @Test - public void testXmlString_serializeInvalidFormulaType() throws Exception { - IntegrityFormula invalidFormula = getInvalidFormula(); - Rule rule = new Rule(invalidFormula, Rule.DENY); - RuleSerializer xmlSerializer = new RuleXmlSerializer(); - - assertExpectException( - RuleSerializeException.class, - /* expectedExceptionMessageRegex */ "Malformed rule identified.", - () -> - xmlSerializer.serialize( - Collections.singletonList(rule), - /* formatVersion= */ Optional.empty())); - } - - private String generateTagWithAttribute( - String tag, Map<String, String> attributeValues, boolean closed) { - StringBuilder res = new StringBuilder("<"); - res.append(tag); - for (String attribute : attributeValues.keySet()) { - res.append(" "); - res.append(attribute); - res.append("=\""); - res.append(attributeValues.get(attribute)); - res.append("\""); - } - res.append(closed ? " />" : ">"); - return res.toString(); - } - - private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, - packageName, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - SAMPLE_INSTALLER_NAME, - /* isHashedValue= */ false))), - Rule.DENY); - } - - private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName( - String packageName) { - - Map<String, String> packageNameAttrs = new LinkedHashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.PACKAGE_NAME)); - packageNameAttrs.put("V", packageName); - packageNameAttrs.put("H", "false"); - - Map<String, String> installerNameAttrs = new LinkedHashMap<>(); - installerNameAttrs.put("K", String.valueOf(AtomicFormula.INSTALLER_NAME)); - installerNameAttrs.put("V", SAMPLE_INSTALLER_NAME); - installerNameAttrs.put("H", "false"); - - return generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.AND)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + generateTagWithAttribute( - /* tag= */ "AF", installerNameAttrs, /* closed= */ true) - + "</OF>" - + "</R>"; - } - - - private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) { - return new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, - certificate, - /* isHashedValue= */ false), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_NAME, - SAMPLE_INSTALLER_NAME, - /* isHashedValue= */ false))), - Rule.DENY); - } - - private String getSerializedCompoundRuleWithAppCertificateAndSampleInstallerName( - String appCert) { - - Map<String, String> packageNameAttrs = new LinkedHashMap<>(); - packageNameAttrs.put("K", String.valueOf(AtomicFormula.APP_CERTIFICATE)); - packageNameAttrs.put("V", appCert); - packageNameAttrs.put("H", "false"); - - Map<String, String> installerNameAttrs = new LinkedHashMap<>(); - installerNameAttrs.put("K", String.valueOf(AtomicFormula.INSTALLER_NAME)); - installerNameAttrs.put("V", SAMPLE_INSTALLER_NAME); - installerNameAttrs.put("H", "false"); - - return generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.AND)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", packageNameAttrs, /* closed= */ true) - + generateTagWithAttribute( - /* tag= */ "AF", installerNameAttrs, /* closed= */ true) - + "</OF>" - + "</R>"; - } - - private String getSerializedCompoundRuleWithSampleInstallerNameAndCert() { - Map<String, String> installerNameAttrs = new LinkedHashMap<>(); - installerNameAttrs.put("K", String.valueOf(AtomicFormula.INSTALLER_NAME)); - installerNameAttrs.put("V", SAMPLE_INSTALLER_NAME); - installerNameAttrs.put("H", "false"); - - Map<String, String> installerCertAttrs = new LinkedHashMap<>(); - installerCertAttrs.put("K", String.valueOf(AtomicFormula.INSTALLER_CERTIFICATE)); - installerCertAttrs.put("V", SAMPLE_INSTALLER_CERT); - installerCertAttrs.put("H", "false"); - - return generateTagWithAttribute( - /* tag= */ "R", - Collections.singletonMap("E", String.valueOf(Rule.DENY)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "OF", - Collections.singletonMap("C", String.valueOf(CompoundFormula.AND)), - /* closed= */ false) - + generateTagWithAttribute( - /* tag= */ "AF", installerNameAttrs, /* closed= */ true) - + generateTagWithAttribute( - /* tag= */ "AF", installerCertAttrs, /* closed= */ true) - + "</OF>" - + "</R>"; - } - - private IntegrityFormula getInvalidFormula() { - return new AtomicFormula(0) { - @Override - public int getTag() { - return 0; - } - - @Override - public boolean matches(AppInstallMetadata appInstallMetadata) { - return false; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj); - } - - @NonNull - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - @Override - public String toString() { - return super.toString(); - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - } - }; - } -} diff --git a/services/tests/servicestests/src/com/android/server/people/data/AggregateEventHistoryImplTest.java b/services/tests/servicestests/src/com/android/server/people/data/AggregateEventHistoryImplTest.java new file mode 100644 index 000000000000..b614a4f91d28 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/AggregateEventHistoryImplTest.java @@ -0,0 +1,131 @@ +/* + * 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 com.android.server.people.data.TestUtils.timestamp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@RunWith(JUnit4.class) +public final class AggregateEventHistoryImplTest { + + private static final long CURRENT_TIMESTAMP = timestamp("01-30 18:50"); + + private static final Event E1 = new Event(timestamp("01-06 05:26"), + Event.TYPE_NOTIFICATION_OPENED); + private static final Event E2 = new Event(timestamp("01-27 18:41"), + Event.TYPE_NOTIFICATION_OPENED); + private static final Event E3 = new Event(timestamp("01-30 03:06"), + Event.TYPE_SMS_OUTGOING); + private static final Event E4 = new Event(timestamp("01-30 18:14"), + Event.TYPE_SMS_INCOMING); + + private EventHistoryImpl mEventHistory1; + private EventHistoryImpl mEventHistory2; + + private AggregateEventHistoryImpl mAggEventHistory; + + private EventIndex.Injector mInjector = new EventIndex.Injector() { + @Override + long currentTimeMillis() { + return CURRENT_TIMESTAMP; + } + }; + + @Before + public void setUp() { + mAggEventHistory = new AggregateEventHistoryImpl(); + + EventHistoryImpl.Injector injector = new EventHistoryImplInjector(); + + mEventHistory1 = new EventHistoryImpl(injector); + mEventHistory1.addEvent(E1); + mEventHistory1.addEvent(E2); + + mEventHistory2 = new EventHistoryImpl(injector); + mEventHistory2.addEvent(E3); + mEventHistory2.addEvent(E4); + } + + @Test + public void testEmptyAggregateEventHistory() { + assertTrue(mAggEventHistory.getEventIndex(Event.TYPE_SHORTCUT_INVOCATION).isEmpty()); + assertTrue(mAggEventHistory.getEventIndex(Event.ALL_EVENT_TYPES).isEmpty()); + assertTrue(mAggEventHistory.queryEvents( + Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE).isEmpty()); + } + + @Test + public void testQueryEventIndexForSingleEventType() { + mAggEventHistory.addEventHistory(mEventHistory1); + mAggEventHistory.addEventHistory(mEventHistory2); + + EventIndex eventIndex; + + eventIndex = mAggEventHistory.getEventIndex(Event.TYPE_NOTIFICATION_OPENED); + assertEquals(2, eventIndex.getActiveTimeSlots().size()); + + eventIndex = mAggEventHistory.getEventIndex(Event.TYPE_SMS_OUTGOING); + assertEquals(1, eventIndex.getActiveTimeSlots().size()); + + eventIndex = mAggEventHistory.getEventIndex(Event.TYPE_SHORTCUT_INVOCATION); + assertTrue(eventIndex.isEmpty()); + } + + @Test + public void testQueryEventIndexForMultipleEventTypes() { + mAggEventHistory.addEventHistory(mEventHistory1); + mAggEventHistory.addEventHistory(mEventHistory2); + + EventIndex eventIndex; + + eventIndex = mAggEventHistory.getEventIndex(Event.SMS_EVENT_TYPES); + assertEquals(2, eventIndex.getActiveTimeSlots().size()); + + eventIndex = mAggEventHistory.getEventIndex(Event.ALL_EVENT_TYPES); + assertEquals(4, eventIndex.getActiveTimeSlots().size()); + } + + @Test + public void testQueryEvents() { + mAggEventHistory.addEventHistory(mEventHistory1); + mAggEventHistory.addEventHistory(mEventHistory2); + + List<Event> events; + + events = mAggEventHistory.queryEvents(Event.NOTIFICATION_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(2, events.size()); + + events = mAggEventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(4, events.size()); + } + + private class EventHistoryImplInjector extends EventHistoryImpl.Injector { + + EventIndex createEventIndex() { + return new EventIndex(mInjector); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java new file mode 100644 index 000000000000..96302b954e75 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java @@ -0,0 +1,187 @@ +/* + * 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.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; +import android.test.mock.MockContentProvider; +import android.test.mock.MockContentResolver; +import android.test.mock.MockContext; +import android.util.ArrayMap; + +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.Map; + +@RunWith(JUnit4.class) +public final class ContactsQueryHelperTest { + + private static final String CONTACT_LOOKUP_KEY = "123"; + private static final String PHONE_NUMBER = "+1234567890"; + + private static final String[] CONTACTS_COLUMNS = new String[] { + Contacts._ID, Contacts.LOOKUP_KEY, Contacts.STARRED, Contacts.HAS_PHONE_NUMBER, + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP }; + private static final String[] CONTACTS_LOOKUP_COLUMNS = new String[] { + Contacts._ID, Contacts.LOOKUP_KEY, Contacts.STARRED, Contacts.HAS_PHONE_NUMBER }; + private static final String[] PHONE_COLUMNS = new String[] { + ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }; + + @Mock + private MockContext mContext; + + private MatrixCursor mContactsCursor; + private MatrixCursor mContactsLookupCursor; + private MatrixCursor mPhoneCursor; + private ContactsQueryHelper mHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContactsCursor = new MatrixCursor(CONTACTS_COLUMNS); + mContactsLookupCursor = new MatrixCursor(CONTACTS_LOOKUP_COLUMNS); + mPhoneCursor = new MatrixCursor(PHONE_COLUMNS); + + MockContentResolver contentResolver = new MockContentResolver(); + ContactsContentProvider contentProvider = new ContactsContentProvider(); + contentProvider.registerCursor(Contacts.CONTENT_URI, mContactsCursor); + contentProvider.registerCursor( + ContactsContract.PhoneLookup.CONTENT_FILTER_URI, mContactsLookupCursor); + contentProvider.registerCursor( + ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI, mContactsLookupCursor); + contentProvider.registerCursor( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, mPhoneCursor); + + contentResolver.addProvider(ContactsContract.AUTHORITY, contentProvider); + when(mContext.getContentResolver()).thenReturn(contentResolver); + + mHelper = new ContactsQueryHelper(mContext); + } + + @Test + public void testQueryWithUri() { + mContactsCursor.addRow(new Object[] { + /* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 1, + /* lastUpdatedTimestamp= */ 100L }); + mPhoneCursor.addRow(new String[] { PHONE_NUMBER }); + Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY); + assertTrue(mHelper.query(contactUri.toString())); + assertNotNull(mHelper.getContactUri()); + assertEquals(PHONE_NUMBER, mHelper.getPhoneNumber()); + assertEquals(100L, mHelper.getLastUpdatedTimestamp()); + assertTrue(mHelper.isStarred()); + } + + @Test + public void testQueryWithUriNotStarredNoPhoneNumber() { + mContactsCursor.addRow(new Object[] { + /* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 0, /* hasPhoneNumber= */ 0, + /* lastUpdatedTimestamp= */ 100L }); + Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY); + assertTrue(mHelper.query(contactUri.toString())); + assertNotNull(mHelper.getContactUri()); + assertNull(mHelper.getPhoneNumber()); + assertFalse(mHelper.isStarred()); + assertEquals(100L, mHelper.getLastUpdatedTimestamp()); + } + + @Test + public void testQueryWithUriNotFound() { + Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY); + assertFalse(mHelper.query(contactUri.toString())); + } + + @Test + public void testQueryWithPhoneNumber() { + mContactsLookupCursor.addRow(new Object[] { + /* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 1 }); + mPhoneCursor.addRow(new String[] { PHONE_NUMBER }); + String contactUri = "tel:" + PHONE_NUMBER; + assertTrue(mHelper.query(contactUri)); + assertNotNull(mHelper.getContactUri()); + assertEquals(PHONE_NUMBER, mHelper.getPhoneNumber()); + assertTrue(mHelper.isStarred()); + } + + @Test + public void testQueryWithEmail() { + mContactsLookupCursor.addRow(new Object[] { + /* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 0 }); + String contactUri = "mailto:test@gmail.com"; + assertTrue(mHelper.query(contactUri)); + assertNotNull(mHelper.getContactUri()); + assertNull(mHelper.getPhoneNumber()); + assertTrue(mHelper.isStarred()); + } + + @Test + public void testQueryUpdatedContactSinceTime() { + mContactsCursor.addRow(new Object[] { + /* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 0, + /* lastUpdatedTimestamp= */ 100L }); + assertTrue(mHelper.querySince(50L)); + assertNotNull(mHelper.getContactUri()); + assertNull(mHelper.getPhoneNumber()); + assertTrue(mHelper.isStarred()); + assertEquals(100L, mHelper.getLastUpdatedTimestamp()); + } + + @Test + public void testQueryWithUnsupportedScheme() { + mContactsLookupCursor.addRow(new Object[] { + /* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 1 }); + mPhoneCursor.addRow(new String[] { PHONE_NUMBER }); + String contactUri = "unknown:test"; + assertFalse(mHelper.query(contactUri)); + } + + private class ContactsContentProvider extends MockContentProvider { + + private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>(); + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) { + if (uri.isPathPrefixMatch(prefixUri)) { + return mUriPrefixToCursorMap.get(prefixUri); + } + } + return mUriPrefixToCursorMap.get(uri); + } + + private void registerCursor(Uri uriPrefix, Cursor cursor) { + mUriPrefixToCursorMap.put(uriPrefix, cursor); + } + } +} 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 new file mode 100644 index 000000000000..05a9a80e262c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java @@ -0,0 +1,130 @@ +/* + * 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.assertNull; +import static org.junit.Assert.assertTrue; + +import android.content.LocusId; +import android.content.pm.ShortcutInfo; +import android.net.Uri; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ConversationInfoTest { + + private static final String SHORTCUT_ID = "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"; + private static final String NOTIFICATION_CHANNEL_ID = "test : abc"; + + @Test + public void testBuild() { + ConversationInfo conversationInfo = new ConversationInfo.Builder() + .setShortcutId(SHORTCUT_ID) + .setLocusId(LOCUS_ID) + .setContactUri(CONTACT_URI) + .setContactPhoneNumber(PHONE_NUMBER) + .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) + .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) + .setVip(true) + .setNotificationSilenced(true) + .setBubbled(true) + .setDemoted(true) + .setPersonImportant(true) + .setPersonBot(true) + .setContactStarred(true) + .build(); + + assertEquals(SHORTCUT_ID, conversationInfo.getShortcutId()); + assertEquals(LOCUS_ID, conversationInfo.getLocusId()); + assertEquals(CONTACT_URI, conversationInfo.getContactUri()); + assertEquals(PHONE_NUMBER, conversationInfo.getContactPhoneNumber()); + assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId()); + assertTrue(conversationInfo.isShortcutLongLived()); + assertTrue(conversationInfo.isVip()); + assertTrue(conversationInfo.isNotificationSilenced()); + assertTrue(conversationInfo.isBubbled()); + assertTrue(conversationInfo.isDemoted()); + assertTrue(conversationInfo.isPersonImportant()); + assertTrue(conversationInfo.isPersonBot()); + assertTrue(conversationInfo.isContactStarred()); + } + + @Test + public void testBuildEmpty() { + ConversationInfo conversationInfo = new ConversationInfo.Builder() + .setShortcutId(SHORTCUT_ID) + .build(); + + assertEquals(SHORTCUT_ID, conversationInfo.getShortcutId()); + assertNull(conversationInfo.getLocusId()); + assertNull(conversationInfo.getContactUri()); + assertNull(conversationInfo.getContactPhoneNumber()); + assertNull(conversationInfo.getNotificationChannelId()); + assertFalse(conversationInfo.isShortcutLongLived()); + assertFalse(conversationInfo.isVip()); + assertFalse(conversationInfo.isNotificationSilenced()); + assertFalse(conversationInfo.isBubbled()); + assertFalse(conversationInfo.isDemoted()); + assertFalse(conversationInfo.isPersonImportant()); + assertFalse(conversationInfo.isPersonBot()); + assertFalse(conversationInfo.isContactStarred()); + } + + @Test + public void testBuildFromAnotherConversationInfo() { + ConversationInfo source = new ConversationInfo.Builder() + .setShortcutId(SHORTCUT_ID) + .setLocusId(LOCUS_ID) + .setContactUri(CONTACT_URI) + .setContactPhoneNumber(PHONE_NUMBER) + .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) + .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) + .setVip(true) + .setNotificationSilenced(true) + .setBubbled(true) + .setPersonImportant(true) + .setPersonBot(true) + .setContactStarred(true) + .build(); + + ConversationInfo destination = new ConversationInfo.Builder(source) + .setVip(false) + .setContactStarred(false) + .build(); + + assertEquals(SHORTCUT_ID, destination.getShortcutId()); + assertEquals(LOCUS_ID, destination.getLocusId()); + assertEquals(CONTACT_URI, destination.getContactUri()); + assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber()); + assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId()); + assertTrue(destination.isShortcutLongLived()); + assertFalse(destination.isVip()); + assertTrue(destination.isNotificationSilenced()); + assertTrue(destination.isBubbled()); + assertTrue(destination.isPersonImportant()); + assertTrue(destination.isPersonBot()); + assertFalse(destination.isContactStarred()); + } +} 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 new file mode 100644 index 000000000000..a40c6ab90197 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java @@ -0,0 +1,153 @@ +/* + * 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.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.content.LocusId; +import android.content.pm.ShortcutInfo; +import android.net.Uri; +import android.util.ArraySet; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Set; + +@RunWith(JUnit4.class) +public final class ConversationStoreTest { + + private static final String SHORTCUT_ID = "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"; + + private ConversationStore mConversationStore; + + @Before + public void setUp() { + mConversationStore = new ConversationStore(); + } + + @Test + public void testAddConversation() { + mConversationStore.addOrUpdate(buildConversationInfo(SHORTCUT_ID)); + + ConversationInfo out = mConversationStore.getConversation(SHORTCUT_ID); + assertNotNull(out); + assertEquals(SHORTCUT_ID, out.getShortcutId()); + } + + @Test + public void testUpdateConversation() { + ConversationInfo original = + buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + mConversationStore.addOrUpdate(original); + assertEquals(LOCUS_ID, mConversationStore.getConversation(SHORTCUT_ID).getLocusId()); + + LocusId newLocusId = new LocusId("ghi"); + ConversationInfo update = buildConversationInfo( + SHORTCUT_ID, newLocusId, CONTACT_URI, PHONE_NUMBER); + mConversationStore.addOrUpdate(update); + assertEquals(newLocusId, mConversationStore.getConversation(SHORTCUT_ID).getLocusId()); + } + + @Test + public void testDeleteConversation() { + mConversationStore.addOrUpdate(buildConversationInfo(SHORTCUT_ID)); + assertNotNull(mConversationStore.getConversation(SHORTCUT_ID)); + + mConversationStore.deleteConversation(SHORTCUT_ID); + assertNull(mConversationStore.getConversation(SHORTCUT_ID)); + } + + @Test + public void testForAllConversations() { + mConversationStore.addOrUpdate(buildConversationInfo("a")); + mConversationStore.addOrUpdate(buildConversationInfo("b")); + mConversationStore.addOrUpdate(buildConversationInfo("c")); + + Set<String> shortcutIds = new ArraySet<>(); + + mConversationStore.forAllConversations( + conversationInfo -> shortcutIds.add(conversationInfo.getShortcutId())); + assertTrue(shortcutIds.contains("a")); + assertTrue(shortcutIds.contains("b")); + assertTrue(shortcutIds.contains("c")); + } + + @Test + public void testGetConversationByLocusId() { + ConversationInfo in = + buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + mConversationStore.addOrUpdate(in); + ConversationInfo out = mConversationStore.getConversationByLocusId(LOCUS_ID); + assertNotNull(out); + assertEquals(SHORTCUT_ID, out.getShortcutId()); + + mConversationStore.deleteConversation(SHORTCUT_ID); + assertNull(mConversationStore.getConversationByLocusId(LOCUS_ID)); + } + + @Test + public void testGetConversationByContactUri() { + ConversationInfo in = + buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + mConversationStore.addOrUpdate(in); + ConversationInfo out = mConversationStore.getConversationByContactUri(CONTACT_URI); + assertNotNull(out); + assertEquals(SHORTCUT_ID, out.getShortcutId()); + + mConversationStore.deleteConversation(SHORTCUT_ID); + assertNull(mConversationStore.getConversationByContactUri(CONTACT_URI)); + } + + @Test + public void testGetConversationByPhoneNumber() { + ConversationInfo in = + buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, PHONE_NUMBER); + mConversationStore.addOrUpdate(in); + ConversationInfo out = mConversationStore.getConversationByPhoneNumber(PHONE_NUMBER); + assertNotNull(out); + assertEquals(SHORTCUT_ID, out.getShortcutId()); + + mConversationStore.deleteConversation(SHORTCUT_ID); + assertNull(mConversationStore.getConversationByPhoneNumber(PHONE_NUMBER)); + } + + private static ConversationInfo buildConversationInfo(String shortcutId) { + return buildConversationInfo(shortcutId, null, null, null); + } + + private static ConversationInfo buildConversationInfo( + String shortcutId, LocusId locusId, Uri contactUri, String phoneNumber) { + return new ConversationInfo.Builder() + .setShortcutId(shortcutId) + .setLocusId(locusId) + .setContactUri(contactUri) + .setContactPhoneNumber(phoneNumber) + .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) + .setVip(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 new file mode 100644 index 000000000000..9f3d656188e1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -0,0 +1,451 @@ +/* + * 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 android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION; + +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.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Notification; +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.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.content.pm.ShortcutManager.ShareShortcutInfo; +import android.content.pm.ShortcutServiceInternal; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.ContactsContract; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.telephony.TelephonyManager; +import android.util.Range; + +import com.android.internal.app.ChooserActivity; +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.Collections; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@RunWith(JUnit4.class) +public final class DataManagerTest { + + private static final int USER_ID_PRIMARY = 0; + private static final int USER_ID_PRIMARY_MANAGED = 10; + private static final int USER_ID_SECONDARY = 11; + private static final String TEST_PKG_NAME = "pkg"; + 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"; + + @Mock private Context mContext; + @Mock private ShortcutServiceInternal mShortcutServiceInternal; + @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal; + @Mock private ShortcutManager mShortcutManager; + @Mock private UserManager mUserManager; + @Mock private TelephonyManager mTelephonyManager; + @Mock private ContentResolver mContentResolver; + @Mock private ScheduledExecutorService mExecutorService; + @Mock private ScheduledFuture mScheduledFuture; + @Mock private StatusBarNotification mStatusBarNotification; + @Mock private Notification mNotification; + + private DataManager mDataManager; + private int mCallingUserId; + private TestInjector mInjector; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + addLocalServiceMock(ShortcutServiceInternal.class, mShortcutServiceInternal); + + addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal); + + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + + when(mContext.getSystemService(Context.SHORTCUT_SERVICE)).thenReturn(mShortcutManager); + when(mContext.getSystemServiceName(ShortcutManager.class)).thenReturn( + Context.SHORTCUT_SERVICE); + + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mContext.getSystemServiceName(UserManager.class)).thenReturn( + Context.USER_SERVICE); + + when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager); + + when(mExecutorService.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any( + TimeUnit.class))).thenReturn(mScheduledFuture); + + when(mUserManager.getEnabledProfiles(USER_ID_PRIMARY)) + .thenReturn(Arrays.asList( + buildUserInfo(USER_ID_PRIMARY), + buildUserInfo(USER_ID_PRIMARY_MANAGED))); + when(mUserManager.getEnabledProfiles(USER_ID_SECONDARY)) + .thenReturn(Collections.singletonList(buildUserInfo(USER_ID_SECONDARY))); + + when(mContext.getContentResolver()).thenReturn(mContentResolver); + + when(mStatusBarNotification.getNotification()).thenReturn(mNotification); + when(mStatusBarNotification.getPackageName()).thenReturn(TEST_PKG_NAME); + when(mStatusBarNotification.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY)); + when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID); + + mCallingUserId = USER_ID_PRIMARY; + + mInjector = new TestInjector(); + mDataManager = new DataManager(mContext, mInjector); + mDataManager.initialize(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(ShortcutServiceInternal.class); + LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); + } + + @Test + public void testAccessConversationFromTheSameProfileGroup() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_PRIMARY_MANAGED); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); + + mDataManager.onShortcutAddedOrUpdated( + buildShortcutInfo("pkg_1", USER_ID_PRIMARY, "sc_1", + buildPerson(true, false))); + mDataManager.onShortcutAddedOrUpdated( + buildShortcutInfo("pkg_2", USER_ID_PRIMARY_MANAGED, "sc_2", + buildPerson(false, true))); + mDataManager.onShortcutAddedOrUpdated( + buildShortcutInfo("pkg_3", USER_ID_SECONDARY, "sc_3", buildPerson())); + + List<ConversationInfo> conversations = new ArrayList<>(); + mDataManager.forAllPackages( + packageData -> packageData.forAllConversations(conversations::add)); + + // USER_ID_SECONDARY is not in the same profile group as USER_ID_PRIMARY. + assertEquals(2, conversations.size()); + + assertEquals("sc_1", conversations.get(0).getShortcutId()); + assertTrue(conversations.get(0).isPersonImportant()); + assertFalse(conversations.get(0).isPersonBot()); + assertFalse(conversations.get(0).isContactStarred()); + assertEquals(PHONE_NUMBER, conversations.get(0).getContactPhoneNumber()); + + assertEquals("sc_2", conversations.get(1).getShortcutId()); + assertFalse(conversations.get(1).isPersonImportant()); + assertTrue(conversations.get(1).isPersonBot()); + assertFalse(conversations.get(0).isContactStarred()); + assertEquals(PHONE_NUMBER, conversations.get(0).getContactPhoneNumber()); + } + + @Test + public void testAccessConversationForUnlockedUsersOnly() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onShortcutAddedOrUpdated( + buildShortcutInfo("pkg_1", USER_ID_PRIMARY, "sc_1", buildPerson())); + mDataManager.onShortcutAddedOrUpdated( + buildShortcutInfo("pkg_2", USER_ID_PRIMARY_MANAGED, "sc_2", buildPerson())); + + List<ConversationInfo> conversations = new ArrayList<>(); + mDataManager.forAllPackages( + packageData -> packageData.forAllConversations(conversations::add)); + + // USER_ID_PRIMARY_MANAGED is not locked, so only USER_ID_PRIMARY's conversation is stored. + assertEquals(1, conversations.size()); + assertEquals("sc_1", conversations.get(0).getShortcutId()); + + mDataManager.onUserStopped(USER_ID_PRIMARY); + conversations.clear(); + mDataManager.forAllPackages( + packageData -> packageData.forAllConversations(conversations::add)); + assertTrue(conversations.isEmpty()); + } + + @Test + public void testGetShortcut() { + 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()); + } + + @Test + public void testGetShareTargets() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut1 = + buildShortcutInfo("pkg_1", USER_ID_PRIMARY, "sc_1", buildPerson()); + ShareShortcutInfo shareShortcut1 = + new ShareShortcutInfo(shortcut1, new ComponentName("pkg_1", "activity")); + + ShortcutInfo shortcut2 = + buildShortcutInfo("pkg_2", USER_ID_PRIMARY, "sc_2", buildPerson()); + ShareShortcutInfo shareShortcut2 = + new ShareShortcutInfo(shortcut2, new ComponentName("pkg_2", "activity")); + mDataManager.onShortcutAddedOrUpdated(shortcut2); + + when(mShortcutManager.getShareTargets(any(IntentFilter.class))) + .thenReturn(Arrays.asList(shareShortcut1, shareShortcut2)); + + List<ShareShortcutInfo> shareShortcuts = + mDataManager.getConversationShareTargets(new IntentFilter()); + // Only "sc_2" is stored as a conversation. + assertEquals(1, shareShortcuts.size()); + assertEquals("sc_2", shareShortcuts.get(0).getShortcutInfo().getId()); + } + + @Test + public void testReportAppTargetEvent() throws IntentFilter.MalformedMimeTypeException { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + + AppTarget appTarget = new AppTarget.Builder(new AppTargetId(TEST_SHORTCUT_ID), shortcut) + .build(); + AppTargetEvent appTargetEvent = + new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH) + .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE) + .build(); + IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg"); + mDataManager.reportAppTargetEvent(appTargetEvent, intentFilter); + + List<Range<Long>> activeShareTimeSlots = new ArrayList<>(); + mDataManager.forAllPackages(packageData -> + activeShareTimeSlots.addAll( + packageData.getEventHistory(TEST_SHORTCUT_ID) + .getEventIndex(Event.TYPE_SHARE_IMAGE) + .getActiveTimeSlots())); + assertEquals(1, activeShareTimeSlots.size()); + } + + @Test + public void testContactsChanged() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + + final String newPhoneNumber = "+1000000000"; + mInjector.mContactsQueryHelper.mIsStarred = true; + mInjector.mContactsQueryHelper.mPhoneNumber = newPhoneNumber; + + ContentObserver contentObserver = mDataManager.getContactsContentObserverForTesting( + USER_ID_PRIMARY); + contentObserver.onChange(false, ContactsContract.Contacts.CONTENT_URI, USER_ID_PRIMARY); + + List<ConversationInfo> conversations = new ArrayList<>(); + mDataManager.forAllPackages( + packageData -> packageData.forAllConversations(conversations::add)); + assertEquals(1, conversations.size()); + + assertEquals(TEST_SHORTCUT_ID, conversations.get(0).getShortcutId()); + assertTrue(conversations.get(0).isContactStarred()); + assertEquals(newPhoneNumber, conversations.get(0).getContactPhoneNumber()); + } + + @Test + public void testNotificationListener() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + + listenerService.onNotificationRemoved(mStatusBarNotification, null, + NotificationListenerService.REASON_CLICK); + + List<Range<Long>> activeNotificationOpenTimeSlots = new ArrayList<>(); + mDataManager.forAllPackages(packageData -> + activeNotificationOpenTimeSlots.addAll( + packageData.getEventHistory(TEST_SHORTCUT_ID) + .getEventIndex(Event.TYPE_NOTIFICATION_OPENED) + .getActiveTimeSlots())); + assertEquals(1, activeNotificationOpenTimeSlots.size()); + } + + @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())).thenReturn(usageEvents); + + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + + 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); + + 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()); + } + + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } + + private ShortcutInfo buildShortcutInfo(String packageName, int userId, String id, + @Nullable Person person) { + Context mockContext = mock(Context.class); + when(mockContext.getPackageName()).thenReturn(packageName); + when(mockContext.getUserId()).thenReturn(userId); + when(mockContext.getUser()).thenReturn(UserHandle.of(userId)); + ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mockContext, id) + .setShortLabel(id) + .setIntent(new Intent("TestIntent")); + if (person != null) { + builder.setPersons(new Person[] {person}); + } + return builder.build(); + } + + private Person buildPerson() { + return buildPerson(true, false); + } + + private Person buildPerson(boolean isImportant, boolean isBot) { + return new Person.Builder() + .setImportant(isImportant) + .setBot(isBot) + .setUri(CONTACT_URI) + .build(); + } + + private UserInfo buildUserInfo(int userId) { + return new UserInfo(userId, "", 0); + } + + private class TestContactsQueryHelper extends ContactsQueryHelper { + + private Uri mContactUri; + private boolean mIsStarred; + private String mPhoneNumber; + + TestContactsQueryHelper(Context context) { + super(context); + mContactUri = Uri.parse(CONTACT_URI); + mIsStarred = false; + mPhoneNumber = PHONE_NUMBER; + } + + @Override + boolean query(@NonNull String contactUri) { + return true; + } + + @Override + boolean querySince(long sinceTime) { + return true; + } + + @Override + @Nullable + Uri getContactUri() { + return mContactUri; + } + + @Override + boolean isStarred() { + return mIsStarred; + } + + @Override + @Nullable + String getPhoneNumber() { + return mPhoneNumber; + } + } + + private class TestInjector extends DataManager.Injector { + + private final TestContactsQueryHelper mContactsQueryHelper = + new TestContactsQueryHelper(mContext); + + @Override + ScheduledExecutorService createScheduledExecutor() { + return mExecutorService; + } + + @Override + ContactsQueryHelper createContactsQueryHelper(Context context) { + return mContactsQueryHelper; + } + + @Override + int getCallingUserId() { + return mCallingUserId; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/people/data/EventHistoryImplTest.java b/services/tests/servicestests/src/com/android/server/people/data/EventHistoryImplTest.java new file mode 100644 index 000000000000..43e1001f2aee --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/EventHistoryImplTest.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.server.people.data; + +import static com.android.server.people.data.TestUtils.timestamp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.android.collect.Sets; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@RunWith(JUnit4.class) +public final class EventHistoryImplTest { + + private static final long CURRENT_TIMESTAMP = timestamp("01-30 18:50"); + + private static final Event E1 = new Event(timestamp("01-06 05:26"), + Event.TYPE_NOTIFICATION_OPENED); + private static final Event E2 = new Event(timestamp("01-27 18:41"), + Event.TYPE_NOTIFICATION_OPENED); + private static final Event E3 = new Event(timestamp("01-30 03:06"), + Event.TYPE_SHARE_IMAGE); + private static final Event E4 = new Event(timestamp("01-30 18:14"), + Event.TYPE_SMS_INCOMING); + + private EventHistoryImpl mEventHistory; + + @Before + public void setUp() { + EventIndex.Injector eventIndexInjector = new EventIndex.Injector() { + @Override + long currentTimeMillis() { + return CURRENT_TIMESTAMP; + } + }; + EventHistoryImpl.Injector eventHistoryInjector = new EventHistoryImpl.Injector() { + @Override + EventIndex createEventIndex() { + return new EventIndex(eventIndexInjector); + } + }; + mEventHistory = new EventHistoryImpl(eventHistoryInjector); + } + + @Test + public void testNoEvents() { + EventIndex eventIndex = mEventHistory.getEventIndex(Event.ALL_EVENT_TYPES); + assertTrue(eventIndex.isEmpty()); + + List<Event> events = mEventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, 999L); + assertTrue(events.isEmpty()); + } + + @Test + public void testMultipleEvents() { + mEventHistory.addEvent(E1); + mEventHistory.addEvent(E2); + mEventHistory.addEvent(E3); + mEventHistory.addEvent(E4); + + EventIndex eventIndex = mEventHistory.getEventIndex(Event.ALL_EVENT_TYPES); + assertEquals(4, eventIndex.getActiveTimeSlots().size()); + + List<Event> events = mEventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(4, events.size()); + } + + @Test + public void testQuerySomeEventTypes() { + mEventHistory.addEvent(E1); + mEventHistory.addEvent(E2); + mEventHistory.addEvent(E3); + mEventHistory.addEvent(E4); + + EventIndex eventIndex = mEventHistory.getEventIndex(Event.NOTIFICATION_EVENT_TYPES); + assertEquals(2, eventIndex.getActiveTimeSlots().size()); + + List<Event> events = mEventHistory.queryEvents( + Event.NOTIFICATION_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(2, events.size()); + } + + @Test + public void testQuerySingleEventType() { + mEventHistory.addEvent(E1); + mEventHistory.addEvent(E2); + mEventHistory.addEvent(E3); + mEventHistory.addEvent(E4); + + EventIndex eventIndex = mEventHistory.getEventIndex(Event.TYPE_SHARE_IMAGE); + assertEquals(1, eventIndex.getActiveTimeSlots().size()); + + List<Event> events = mEventHistory.queryEvents( + Sets.newArraySet(Event.TYPE_SHARE_IMAGE), 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/people/data/EventIndexTest.java b/services/tests/servicestests/src/com/android/server/people/data/EventIndexTest.java new file mode 100644 index 000000000000..e87f428d09bb --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/EventIndexTest.java @@ -0,0 +1,176 @@ +/* + * 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 com.android.server.people.data.TestUtils.timestamp; + +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 android.util.Range; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@RunWith(JUnit4.class) +public final class EventIndexTest { + + private static final long CURRENT_TIMESTAMP = timestamp("01-30 18:50"); + private static final long SECONDS_PER_HOUR = 60L * 60L; + private static final long SECONDS_PER_DAY = SECONDS_PER_HOUR * 24L; + + private TestInjector mInjector; + private EventIndex mEventIndex; + + @Before + public void setUp() { + mInjector = new TestInjector(CURRENT_TIMESTAMP); + mEventIndex = new EventIndex(mInjector); + } + + @Test + public void testNoEvents() { + assertTrue(mEventIndex.isEmpty()); + assertNull(mEventIndex.getMostRecentActiveTimeSlot()); + assertTrue(mEventIndex.getActiveTimeSlots().isEmpty()); + } + + @Test + public void testMultipleEvents() { + mEventIndex.addEvent(timestamp("01-06 05:26")); + mEventIndex.addEvent(timestamp("01-27 18:41")); + mEventIndex.addEvent(timestamp("01-30 03:06")); + mEventIndex.addEvent(timestamp("01-30 18:14")); + + assertFalse(mEventIndex.isEmpty()); + Range<Long> mostRecentSlot = mEventIndex.getMostRecentActiveTimeSlot(); + assertNotNull(mostRecentSlot); + assertTimeSlot(timestamp("01-30 18:14"), timestamp("01-30 18:16"), mostRecentSlot); + + List<Range<Long>> slots = mEventIndex.getActiveTimeSlots(); + assertEquals(4, slots.size()); + assertTimeSlot(timestamp("01-06 00:00"), timestamp("01-07 00:00"), slots.get(0)); + assertTimeSlot(timestamp("01-27 16:00"), timestamp("01-27 20:00"), slots.get(1)); + assertTimeSlot(timestamp("01-30 03:00"), timestamp("01-30 04:00"), slots.get(2)); + assertTimeSlot(timestamp("01-30 18:14"), timestamp("01-30 18:16"), slots.get(3)); + } + + @Test + public void testBitmapShift() { + mEventIndex.addEvent(CURRENT_TIMESTAMP); + List<Range<Long>> slots; + + slots = mEventIndex.getActiveTimeSlots(); + assertEquals(1, slots.size()); + assertTimeSlot(timestamp("01-30 18:50"), timestamp("01-30 18:52"), slots.get(0)); + + mInjector.moveTimeForwardSeconds(SECONDS_PER_HOUR * 3L); + mEventIndex.update(); + slots = mEventIndex.getActiveTimeSlots(); + assertEquals(1, slots.size()); + assertTimeSlot(timestamp("01-30 18:00"), timestamp("01-30 19:00"), slots.get(0)); + + mInjector.moveTimeForwardSeconds(SECONDS_PER_DAY * 6L); + mEventIndex.update(); + slots = mEventIndex.getActiveTimeSlots(); + assertEquals(1, slots.size()); + assertTimeSlot(timestamp("01-30 16:00"), timestamp("01-30 20:00"), slots.get(0)); + + mInjector.moveTimeForwardSeconds(SECONDS_PER_DAY * 30L); + mEventIndex.update(); + slots = mEventIndex.getActiveTimeSlots(); + assertEquals(1, slots.size()); + assertTimeSlot(timestamp("01-30 00:00"), timestamp("01-31 00:00"), slots.get(0)); + + mInjector.moveTimeForwardSeconds(SECONDS_PER_DAY * 80L); + mEventIndex.update(); + slots = mEventIndex.getActiveTimeSlots(); + // The event has been shifted off the left end. + assertTrue(slots.isEmpty()); + } + + @Test + public void testCopyConstructor() { + mEventIndex.addEvent(timestamp("01-06 05:26")); + mEventIndex.addEvent(timestamp("01-27 18:41")); + mEventIndex.addEvent(timestamp("01-30 03:06")); + mEventIndex.addEvent(timestamp("01-30 18:14")); + + List<Range<Long>> slots = mEventIndex.getActiveTimeSlots(); + + EventIndex newIndex = new EventIndex(mEventIndex); + List<Range<Long>> newSlots = newIndex.getActiveTimeSlots(); + + assertEquals(slots.size(), newSlots.size()); + for (int i = 0; i < slots.size(); i++) { + assertEquals(slots.get(i), newSlots.get(i)); + } + } + + @Test + public void combineEventIndexes() { + EventIndex a = new EventIndex(mInjector); + mInjector.mCurrentTimeMillis = timestamp("01-27 18:41"); + a.addEvent(mInjector.mCurrentTimeMillis); + mInjector.mCurrentTimeMillis = timestamp("01-30 03:06"); + a.addEvent(mInjector.mCurrentTimeMillis); + + mInjector.mCurrentTimeMillis = CURRENT_TIMESTAMP; + EventIndex b = new EventIndex(mInjector); + b.addEvent(timestamp("01-06 05:26")); + b.addEvent(timestamp("01-30 18:14")); + + EventIndex combined = EventIndex.combine(a, b); + List<Range<Long>> slots = combined.getActiveTimeSlots(); + assertEquals(4, slots.size()); + assertTimeSlot(timestamp("01-06 00:00"), timestamp("01-07 00:00"), slots.get(0)); + assertTimeSlot(timestamp("01-27 16:00"), timestamp("01-27 20:00"), slots.get(1)); + assertTimeSlot(timestamp("01-30 03:00"), timestamp("01-30 04:00"), slots.get(2)); + assertTimeSlot(timestamp("01-30 18:14"), timestamp("01-30 18:16"), slots.get(3)); + } + + private static void assertTimeSlot( + long expectedLower, long expectedUpper, Range<Long> actualSlot) { + assertEquals(expectedLower, actualSlot.getLower().longValue()); + assertEquals(expectedUpper, actualSlot.getUpper().longValue()); + } + + private class TestInjector extends EventIndex.Injector { + + private long mCurrentTimeMillis; + + TestInjector(long currentTimeMillis) { + mCurrentTimeMillis = currentTimeMillis; + } + + private void moveTimeForwardSeconds(long seconds) { + mCurrentTimeMillis += (seconds * 1000L); + } + + @Override + long currentTimeMillis() { + return mCurrentTimeMillis; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/people/data/EventListTest.java b/services/tests/servicestests/src/com/android/server/people/data/EventListTest.java new file mode 100644 index 000000000000..f2f372c1b8c3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/EventListTest.java @@ -0,0 +1,137 @@ +/* + * 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.assertTrue; + +import com.google.android.collect.Lists; +import com.google.android.collect.Sets; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@RunWith(JUnit4.class) +public final class EventListTest { + + private static final Event E1 = new Event(101L, Event.TYPE_NOTIFICATION_OPENED); + private static final Event E2 = new Event(103L, Event.TYPE_NOTIFICATION_OPENED); + private static final Event E3 = new Event(107L, Event.TYPE_SHARE_IMAGE); + private static final Event E4 = new Event(109L, Event.TYPE_SMS_INCOMING); + + private EventList mEventList; + + @Before + public void setUp() { + mEventList = new EventList(); + } + + @Test + public void testQueryEmptyEventList() { + List<Event> events = mEventList.queryEvents(Event.ALL_EVENT_TYPES, 0L, 999L); + assertTrue(events.isEmpty()); + } + + @Test + public void testAddAndQueryEvents() { + List<Event> in = Lists.newArrayList(E1, E2, E3, E4); + for (Event e : in) { + mEventList.add(e); + } + + List<Event> out = mEventList.queryEvents(Event.ALL_EVENT_TYPES, 0L, 999L); + assertEventListEquals(in, out); + } + + @Test + public void testAddEventsNotInOrder() { + mEventList.add(E3); + mEventList.add(E1); + mEventList.add(E4); + mEventList.add(E2); + + List<Event> out = mEventList.queryEvents(Event.ALL_EVENT_TYPES, 0L, 999L); + List<Event> expected = Lists.newArrayList(E1, E2, E3, E4); + assertEventListEquals(expected, out); + } + + @Test + public void testQueryEventsByType() { + mEventList.add(E1); + mEventList.add(E2); + mEventList.add(E3); + mEventList.add(E4); + + List<Event> out = mEventList.queryEvents( + Sets.newArraySet(Event.TYPE_NOTIFICATION_OPENED), 0L, 999L); + assertEventListEquals(Lists.newArrayList(E1, E2), out); + } + + @Test + public void testQueryEventsByTimeRange() { + mEventList.add(E1); + mEventList.add(E2); + mEventList.add(E3); + mEventList.add(E4); + + List<Event> out = mEventList.queryEvents(Event.ALL_EVENT_TYPES, 103L, 109L); + // Only E2 and E3 are in the time range [103L, 109L). + assertEventListEquals(Lists.newArrayList(E2, E3), out); + } + + @Test + public void testQueryEventsOutOfRange() { + mEventList.add(E1); + mEventList.add(E2); + mEventList.add(E3); + mEventList.add(E4); + + List<Event> out = mEventList.queryEvents(Event.ALL_EVENT_TYPES, 900L, 900L); + assertTrue(out.isEmpty()); + } + + @Test + public void testAddDuplicateEvents() { + mEventList.add(E1); + mEventList.add(E2); + mEventList.add(E2); + mEventList.add(E3); + mEventList.add(E2); + mEventList.add(E3); + mEventList.add(E3); + mEventList.add(E4); + mEventList.add(E1); + mEventList.add(E3); + mEventList.add(E2); + + List<Event> out = mEventList.queryEvents(Event.ALL_EVENT_TYPES, 0L, 999L); + List<Event> expected = Lists.newArrayList(E1, E2, E3, E4); + assertEventListEquals(expected, out); + } + + private static void assertEventListEquals(List<Event> expected, List<Event> actual) { + assertEquals(expected.size(), actual.size()); + for (int i = 0; i < expected.size(); i++) { + assertEquals(expected.get(i).getTimestamp(), actual.get(i).getTimestamp()); + assertEquals(expected.get(i).getType(), actual.get(i).getType()); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java new file mode 100644 index 000000000000..1b80d6fc3a2d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.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.people.data; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.LocusId; +import android.content.pm.ShortcutInfo; +import android.net.Uri; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@RunWith(JUnit4.class) +public final class PackageDataTest { + + private static final String PACKAGE_NAME = "com.google.test"; + private static final int USER_ID = 0; + private static final String SHORTCUT_ID = "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"; + + private Event mE1; + private Event mE2; + private Event mE3; + private Event mE4; + + private PackageData mPackageData; + + @Before + public void setUp() { + mPackageData = new PackageData(PACKAGE_NAME, USER_ID); + ConversationInfo conversationInfo = new ConversationInfo.Builder() + .setShortcutId(SHORTCUT_ID) + .setLocusId(LOCUS_ID) + .setContactUri(CONTACT_URI) + .setContactPhoneNumber(PHONE_NUMBER) + .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) + .build(); + mPackageData.getConversationStore().addOrUpdate(conversationInfo); + + long currentTimestamp = System.currentTimeMillis(); + mE1 = new Event(currentTimestamp - 800L, Event.TYPE_SHORTCUT_INVOCATION); + mE2 = new Event(currentTimestamp - 700L, Event.TYPE_NOTIFICATION_OPENED); + mE3 = new Event(currentTimestamp - 600L, Event.TYPE_CALL_INCOMING); + mE4 = new Event(currentTimestamp - 500L, Event.TYPE_SMS_OUTGOING); + } + + @Test + public void testGetEventHistory() { + EventStore eventStore = mPackageData.getEventStore(); + eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1); + eventStore.getOrCreateLocusEventHistory(LOCUS_ID).addEvent(mE2); + + EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(2, events.size()); + assertEventEquals(mE1, events.get(0)); + assertEventEquals(mE2, events.get(1)); + } + + @Test + public void testGetEventHistoryDefaultDialerAndSmsApp() { + mPackageData.setIsDefaultDialer(true); + mPackageData.setIsDefaultSmsApp(true); + EventStore eventStore = mPackageData.getEventStore(); + eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1); + eventStore.getOrCreateCallEventHistory(PHONE_NUMBER).addEvent(mE3); + eventStore.getOrCreateSmsEventHistory(PHONE_NUMBER).addEvent(mE4); + + assertTrue(mPackageData.isDefaultDialer()); + assertTrue(mPackageData.isDefaultSmsApp()); + EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(3, events.size()); + assertEventEquals(mE1, events.get(0)); + assertEventEquals(mE3, events.get(1)); + assertEventEquals(mE4, events.get(2)); + } + + @Test + public void testGetEventHistoryNotDefaultDialerOrSmsApp() { + EventStore eventStore = mPackageData.getEventStore(); + eventStore.getOrCreateShortcutEventHistory(SHORTCUT_ID).addEvent(mE1); + eventStore.getOrCreateCallEventHistory(PHONE_NUMBER).addEvent(mE3); + eventStore.getOrCreateSmsEventHistory(PHONE_NUMBER).addEvent(mE4); + + assertFalse(mPackageData.isDefaultDialer()); + assertFalse(mPackageData.isDefaultSmsApp()); + EventHistory eventHistory = mPackageData.getEventHistory(SHORTCUT_ID); + List<Event> events = eventHistory.queryEvents(Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE); + assertEquals(1, events.size()); + assertEventEquals(mE1, events.get(0)); + } + + private void assertEventEquals(Event expected, Event actual) { + assertEquals(expected.getTimestamp(), actual.getTimestamp()); + assertEquals(expected.getType(), actual.getType()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/people/data/TestUtils.java b/services/tests/servicestests/src/com/android/server/people/data/TestUtils.java new file mode 100644 index 000000000000..41889aa5b224 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/TestUtils.java @@ -0,0 +1,42 @@ +/* + * 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 java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +final class TestUtils { + + /** + * Gets the epoch time in millis for the specified time string. + * @param timeString e.g. "01-02 15:20" + * @return epoch time in millis + */ + static long timestamp(String timeString) { + String str = String.format("2020-%s", timeString); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + return toEpochMilli(LocalDateTime.parse(str, formatter)); + } + + private static long toEpochMilli(LocalDateTime localDateTime) { + return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + } + + private TestUtils() { + } +} 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 7f66f3c49185..3e3f40d31d0e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -27,6 +27,8 @@ import android.annotation.Nullable; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageParser; +import android.content.pm.Signature; import android.content.pm.parsing.AndroidPackage; import android.content.pm.parsing.ComponentParseUtils; import android.content.pm.parsing.ComponentParseUtils.ParsedActivity; @@ -48,8 +50,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.security.cert.CertificateException; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -284,6 +288,33 @@ public class AppsFilterTest { assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); } + + @Test + public void testSystemSignedTarget_DoesntFilter() throws CertificateException { + final AppsFilter appsFilter = + new AppsFilter(mFeatureConfigMock, new String[]{}, false, null); + appsFilter.onSystemReady(); + + final Signature frameworkSignature = Mockito.mock(Signature.class); + final PackageParser.SigningDetails frameworkSigningDetails = + new PackageParser.SigningDetails(new Signature[]{frameworkSignature}, 1); + + final Signature otherSignature = Mockito.mock(Signature.class); + final PackageParser.SigningDetails otherSigningDetails = + new PackageParser.SigningDetails(new Signature[]{otherSignature}, 1); + + simulateAddPackage(appsFilter, pkg("android"), 1000, + b -> b.setSigningDetails(frameworkSigningDetails)); + PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"), + DUMMY_TARGET_UID, + b -> b.setSigningDetails(frameworkSigningDetails)); + PackageSetting calling = simulateAddPackage(appsFilter, + pkg("com.some.other.package"), DUMMY_CALLING_UID, + b -> b.setSigningDetails(otherSigningDetails)); + + assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); + } + @Test public void testForceQueryableByDevice_NonSystemCaller_Filters() { final AppsFilter appsFilter = diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java index 84414947056f..338d5fa347a6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import android.content.pm.PackageParser; import android.content.pm.PackageUserState; import android.content.pm.parsing.AndroidPackage; import android.util.SparseArray; @@ -42,6 +43,7 @@ public class PackageSettingBuilder { private AndroidPackage mPkg; private int mAppId; private InstallSource mInstallSource; + private PackageParser.SigningDetails mSigningDetails; public PackageSettingBuilder setPackage(AndroidPackage pkg) { this.mPkg = pkg; @@ -143,12 +145,21 @@ public class PackageSettingBuilder { return this; } + public PackageSettingBuilder setSigningDetails( + PackageParser.SigningDetails signingDetails) { + mSigningDetails = signingDetails; + return this; + } + public PackageSetting build() { final PackageSetting packageSetting = new PackageSetting(mName, mRealName, new File(mCodePath), new File(mResourcePath), mLegacyNativeLibraryPathString, mPrimaryCpuAbiString, mSecondaryCpuAbiString, mCpuAbiOverrideString, mPVersionCode, mPkgFlags, mPrivateFlags, mSharedUserId, mUsesStaticLibraries, mUsesStaticLibrariesVersions); + packageSetting.signatures = mSigningDetails != null + ? new PackageSignatures(mSigningDetails) + : new PackageSignatures(); packageSetting.pkg = mPkg; packageSetting.appId = mAppId; packageSetting.volumeUuid = this.mVolumeUuid; diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java new file mode 100644 index 000000000000..a03ba9c5e87e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.os.Looper; +import android.os.PowerManager; +import android.os.test.TestLooper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.TimeZone; + +/** + * Tests for {@link WakeLockLog}. + */ +public class WakeLockLogTest { + + private TestLooper mTestLooper; + + @Before + public void setUp() throws Exception { + mTestLooper = new TestLooper(); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testAddTwoItems() { + final int tagDatabaseSize = 128; + final int logSize = 20; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy); + + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("TagPartial", 101, + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE); + + when(injectorSpy.currentTimeMillis()).thenReturn(1150L); + log.onWakeLockAcquired("TagFull", 102, + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - 101 - ACQ TagPartial (partial,on-after-release)\n" + + " 01-01 00:00:01.150 - 102 - ACQ TagFull (full,acq-causes-wake)\n" + + " -\n" + + " Events: 2, Time-Resets: 0\n" + + " Buffer, Bytes used: 6\n", + dispatchAndDump(log, false)); + } + + @Test + public void testAddTwoItemsWithTimeReset() { + final int tagDatabaseSize = 128; + final int logSize = 20; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy); + + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK); + + when(injectorSpy.currentTimeMillis()).thenReturn(1350L); + log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - 101 - ACQ TagPartial (partial)\n" + + " 01-01 00:00:01.350 - 102 - ACQ TagFull (full)\n" + + " -\n" + + " Events: 2, Time-Resets: 1\n" + + " Buffer, Bytes used: 15\n", + dispatchAndDump(log, false)); + } + + @Test + public void testAddTwoItemsWithTagOverwrite() { + final int tagDatabaseSize = 2; + final int logSize = 20; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy); + + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK); + + when(injectorSpy.currentTimeMillis()).thenReturn(1150L); + log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - --- - ACQ UNKNOWN (partial)\n" + + " 01-01 00:00:01.150 - 102 - ACQ TagFull (full)\n" + + " -\n" + + " Events: 2, Time-Resets: 0\n" + + " Buffer, Bytes used: 6\n", + dispatchAndDump(log, false)); + } + + @Test + public void testAddFourItemsWithRingBufferOverflow() { + final int tagDatabaseSize = 6; + final int logSize = 10; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy); + + // This first item will get deleted when ring buffer loops around + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK); + + when(injectorSpy.currentTimeMillis()).thenReturn(1150L); + log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK); + when(injectorSpy.currentTimeMillis()).thenReturn(1151L); + log.onWakeLockAcquired("TagThree", 101, PowerManager.PARTIAL_WAKE_LOCK); + when(injectorSpy.currentTimeMillis()).thenReturn(1152L); + log.onWakeLockAcquired("TagFour", 101, PowerManager.PARTIAL_WAKE_LOCK); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.150 - 102 - ACQ TagFull (full)\n" + + " 01-01 00:00:01.151 - 101 - ACQ TagThree (partial)\n" + + " 01-01 00:00:01.152 - 101 - ACQ TagFour (partial)\n" + + " -\n" + + " Events: 3, Time-Resets: 0\n" + + " Buffer, Bytes used: 9\n", + dispatchAndDump(log, false)); + } + + @Test + public void testAddItemWithBadTag() { + final int tagDatabaseSize = 6; + final int logSize = 10; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy); + + // Bad tag means it wont get written + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired(null /* tag */, 0 /* ownerUid */, PowerManager.PARTIAL_WAKE_LOCK); + + assertEquals("Wake Lock Log\n" + + " -\n" + + " Events: 0, Time-Resets: 0\n" + + " Buffer, Bytes used: 0\n", + dispatchAndDump(log, false)); + } + + @Test + public void testAddItemWithReducedTagName() { + final int tagDatabaseSize = 6; + final int logSize = 10; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy); + + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("*job*/com.one.two.3hree/.one..Last", 101, + PowerManager.PARTIAL_WAKE_LOCK); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - 101 - ACQ *job*/c.o.t.3/.o..Last (partial)\n" + + " -\n" + + " Events: 1, Time-Resets: 0\n" + + " Buffer, Bytes used: 3\n", + dispatchAndDump(log, false)); + } + + @Test + public void testAddAcquireAndReleaseWithRepeatTagName() { + final int tagDatabaseSize = 6; + final int logSize = 10; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy); + + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK); + when(injectorSpy.currentTimeMillis()).thenReturn(1001L); + log.onWakeLockReleased("HowdyTag", 101); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - 101 - ACQ HowdyTag (partial)\n" + + " 01-01 00:00:01.001 - 101 - REL HowdyTag\n" + + " -\n" + + " Events: 2, Time-Resets: 0\n" + + " Buffer, Bytes used: 5\n" + + " Tag Database: size(5), entries: 1, Bytes used: 80\n", + dispatchAndDump(log, true)); + } + + @Test + public void testAddAcquireAndReleaseWithTimeTravel() { + final int tagDatabaseSize = 6; + final int logSize = 10; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy); + + when(injectorSpy.currentTimeMillis()).thenReturn(1100L); + log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK); + + // New element goes back in time...should not be written to log. + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockReleased("HowdyTag", 101); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.100 - 101 - ACQ HowdyTag (partial)\n" + + " -\n" + + " Events: 1, Time-Resets: 0\n" + + " Buffer, Bytes used: 3\n", + dispatchAndDump(log, false)); + } + + private String dispatchAndDump(WakeLockLog log, boolean includeTagDb) { + mTestLooper.dispatchAll(); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + log.dump(pw, includeTagDb); + return sw.toString(); + } + + public class TestInjector extends WakeLockLog.Injector { + private final int mTagDatabaseSize; + private final int mLogSize; + + public TestInjector(int tagDatabaseSize, int logSize) { + mTagDatabaseSize = tagDatabaseSize; + mLogSize = logSize; + } + + @Override + public Looper getLooper() { + return mTestLooper.getLooper(); + } + + @Override + public int getTagDatabaseSize() { + return mTagDatabaseSize; + } + + @Override + public int getLogSize() { + return mLogSize; + } + + @Override + public SimpleDateFormat getDateFormat() { + SimpleDateFormat format = new SimpleDateFormat(super.getDateFormat().toPattern()); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/utils/TraceBufferTest.java b/services/tests/servicestests/src/com/android/server/utils/TraceBufferTest.java index 09b75e71d946..0e50f2a1b6ad 100644 --- a/services/tests/servicestests/src/com/android/server/utils/TraceBufferTest.java +++ b/services/tests/servicestests/src/com/android/server/utils/TraceBufferTest.java @@ -30,6 +30,7 @@ import android.util.proto.ProtoOutputStream; import androidx.test.filters.SmallTest; import com.android.internal.util.Preconditions; +import com.android.internal.util.TraceBuffer; import org.junit.After; import org.junit.Before; 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 1b92abef7c94..768b4721a1ee 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5438,6 +5438,46 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testOnBubbleNotificationSuppressionChanged() throws Exception { + // Bubble notification + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag"); + + // Bubbles are allowed! + setUpPrefsForBubbles(PKG, nr.sbn.getUserId(), true /* global */, + true /* app */, true /* channel */); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), + nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + waitForIdle(); + + // NOT suppressed + Notification n = mBinderService.getActiveNotifications(PKG)[0].getNotification(); + assertFalse(n.getBubbleMetadata().isNotificationSuppressed()); + + // Reset as this is called when the notif is first sent + reset(mListeners); + + // Test: update suppression to true + mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), true); + waitForIdle(); + + // Check + n = mBinderService.getActiveNotifications(PKG)[0].getNotification(); + assertTrue(n.getBubbleMetadata().isNotificationSuppressed()); + + // Reset to check again + reset(mListeners); + + // Test: update suppression to false + mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), false); + waitForIdle(); + + // Check + n = mBinderService.getActiveNotifications(PKG)[0].getNotification(); + assertFalse(n.getBubbleMetadata().isNotificationSuppressed()); + } + + @Test public void testGrantInlineReplyUriPermission_recordExists() throws Exception { NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0); mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 0fc2bc510d05..bab877e6c26f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -1008,4 +1008,18 @@ public class ActivityStarterTests extends ActivityTestsBase { .setOutActivity(outActivity).execute(); assertThat(outActivity[0].inSplitScreenSecondaryWindowingMode()).isTrue(); } + + @Test + public void testActivityStart_expectAddedToRecentTask() { + RecentTasks recentTasks = mock(RecentTasks.class); + mService.mStackSupervisor.setRecentTasks(recentTasks); + doReturn(true).when(recentTasks).isCallerRecents(anyInt()); + + final ActivityStarter starter = prepareStarter(0 /* flags */); + + starter.setReason("testAddToTaskListOnActivityStart") + .execute(); + + verify(recentTasks, times(1)).add(any()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index b5e7dd58f7cc..a672a95dcc12 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -39,6 +39,8 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -83,6 +85,7 @@ import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; +import java.util.function.Function; /** * Build/Install/Run: @@ -419,6 +422,36 @@ public class RecentTasksTest extends ActivityTestsBase { } @Test + public void testAddTasksHomeClearUntrackedTasks_expectFinish() { + // There may be multiple tasks with the same base intent by flags (FLAG_ACTIVITY_NEW_TASK | + // FLAG_ACTIVITY_MULTIPLE_TASK). If the previous task is still active, it should be removed + // because user may not be able to return to the task. + final String className = ".PermissionsReview"; + final Function<Boolean, Task> taskBuilder = visible -> { + final Task task = createTaskBuilder(className).build(); + // Make the task non-empty. + final ActivityRecord r = new ActivityBuilder(mService).setTask(task).build(); + r.setVisibility(visible); + return task; + }; + + final Task task1 = taskBuilder.apply(false /* visible */); + mRecentTasks.add(task1); + final Task task2 = taskBuilder.apply(true /* visible */); + mRecentTasks.add(task2); + // Only the last task is kept in recents and the previous 2 tasks will becomes untracked + // tasks because their intents are identical. + mRecentTasks.add(createTaskBuilder(className).build()); + // Go home to trigger the removal of untracked tasks. + mRecentTasks.add(createTaskBuilder(".Home").setStack(mDisplay.getRootHomeTask()).build()); + + // All activities in the invisible task should be finishing or removed. + assertNull(task1.getTopNonFinishingActivity()); + // The visible task should not be affected. + assertNotNull(task2.getTopNonFinishingActivity()); + } + + @Test public void testUsersTasks() { mRecentTasks.setOnlyTestVisibleRange(); mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_0_ID); 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 8d2da1e6cb5b..c9fd79fb4e39 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -80,7 +80,6 @@ public class TaskOrganizerTests extends WindowTestsBase { task.setTaskOrganizer(organizer); verify(organizer).taskAppeared(any(), any()); - assertTrue(task.isControlledByTaskOrganizer()); task.removeImmediately(); verify(organizer).taskVanished(any()); @@ -106,48 +105,13 @@ public class TaskOrganizerTests extends WindowTestsBase { final Task task = createTaskInStack(stack, 0 /* userId */); final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); - task.setTaskOrganizer(organizer); - verify(organizer).taskAppeared(any(), any()); - assertTrue(task.isControlledByTaskOrganizer()); - - task.setTaskOrganizer(null); - verify(organizer).taskVanished(any()); - assertFalse(task.isControlledByTaskOrganizer()); - } - - @Test - public void testTransferStackToOrganizer() throws RemoteException { - final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); - final Task task2 = createTaskInStack(stack, 0 /* userId */); - final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); - - stack.transferToTaskOrganizer(organizer); - - verify(organizer, times(2)).taskAppeared(any(), any()); - assertTrue(task.isControlledByTaskOrganizer()); - assertTrue(task2.isControlledByTaskOrganizer()); - - stack.transferToTaskOrganizer(null); - - verify(organizer, times(2)).taskVanished(any()); - assertFalse(task.isControlledByTaskOrganizer()); - assertFalse(task2.isControlledByTaskOrganizer()); - } - - @Test - public void testRegisterTaskOrganizerTaskWindowingModeChanges() throws RemoteException { - final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); - - final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); - final Task task = createTaskInStack(stack, 0 /* userId */); - task.setWindowingMode(WINDOWING_MODE_PINNED); + stack.setTaskOrganizer(organizer); verify(organizer).taskAppeared(any(), any()); - assertTrue(task.isControlledByTaskOrganizer()); + assertTrue(stack.isControlledByTaskOrganizer()); - task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + stack.setTaskOrganizer(null); verify(organizer).taskVanished(any()); - assertFalse(task.isControlledByTaskOrganizer()); + assertFalse(stack.isControlledByTaskOrganizer()); } @Test @@ -158,13 +122,9 @@ public class TaskOrganizerTests extends WindowTestsBase { final Task task = createTaskInStack(stack, 0 /* userId */); final Task task2 = createTaskInStack(stack, 0 /* userId */); stack.setWindowingMode(WINDOWING_MODE_PINNED); - verify(organizer, times(2)).taskAppeared(any(), any()); - assertTrue(task.isControlledByTaskOrganizer()); - assertTrue(task2.isControlledByTaskOrganizer()); + verify(organizer, times(1)).taskAppeared(any(), any()); stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - verify(organizer, times(2)).taskVanished(any()); - assertFalse(task.isControlledByTaskOrganizer()); - assertFalse(task2.isControlledByTaskOrganizer()); + verify(organizer, times(1)).taskVanished(any()); } } diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index a71accf8148e..bb6f154335a9 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -35,7 +35,6 @@ import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; -import android.os.Debug; import android.os.Process; import android.os.UserHandle; import android.provider.Telephony; @@ -195,7 +194,7 @@ public final class SmsApplication { final int callingUid = Binder.getCallingUid(); if (DEBUG_MULTIUSER) { Log.i(LOG_TAG, "getIncomingUserHandle caller=" + callingUid + ", myuid=" - + android.os.Process.myUid() + "\n\t" + Debug.getCallers(4)); + + android.os.Process.myUid()); } if (UserHandle.getAppId(callingUid) < android.os.Process.FIRST_APPLICATION_UID) { @@ -667,9 +666,21 @@ public final class SmsApplication { } /** + * Broadcast action: + * Same as {@link Intent#ACTION_DEFAULT_SMS_PACKAGE_CHANGED} but it's implicit (e.g. sent to + * all apps) and requires + * {@link #PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE} to receive. + */ + public static final String ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL = + "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL"; + + public static final String PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE = + "android.permission.MONITOR_DEFAULT_SMS_PACKAGE"; + + /** * Sends broadcasts on sms app change: * {@link Intent#ACTION_DEFAULT_SMS_PACKAGE_CHANGED} - * {@link Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL} + * {@link #ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL} */ public static void broadcastSmsAppChange(Context context, UserHandle userHandle, @Nullable String oldPackage, @Nullable String newPackage) { @@ -719,11 +730,11 @@ public final class SmsApplication { } // Send an implicit broadcast for the system server. - // (or anyone with MONITOR_DEFAULT_SMS_PACKAGE, really.) + // (or anyone with PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE, really.) final Intent intent = - new Intent(Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL); + new Intent(ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL); context.sendBroadcastAsUser(intent, userHandle, - permission.MONITOR_DEFAULT_SMS_PACKAGE); + PERMISSION_MONITOR_DEFAULT_SMS_PACKAGE); } /** diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java index cd365a113189..95098e89539d 100644 --- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java +++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java @@ -31,6 +31,7 @@ import android.util.Log; import com.android.internal.telephony.HbpcdLookup.MccIdd; import com.android.internal.telephony.HbpcdLookup.MccLookup; +import com.android.internal.telephony.util.TelephonyUtils; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -677,7 +678,7 @@ public class SmsNumberUtils { */ private static String secureHash(byte[] input) { // Refrain from logging user personal information in user build. - if (android.os.Build.IS_USER) { + if (TelephonyUtils.IS_USER) { return "****"; } diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java index a7ad884ca107..682697469af9 100644 --- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java @@ -28,6 +28,8 @@ import android.os.RemoteException; import android.os.SystemProperties; import java.io.PrintWriter; +import java.util.Collections; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -61,6 +63,11 @@ public final class TelephonyUtils { return str == null ? "" : str; } + /** Returns an empty list if the input is {@code null}. */ + public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) { + return cur == null ? Collections.emptyList() : cur; + } + /** Throws a {@link RuntimeException} that wrapps the {@link RemoteException}. */ public static RuntimeException rethrowAsRuntimeException(RemoteException remoteException) { throw new RuntimeException(remoteException); diff --git a/telephony/common/com/google/android/mms/util/DrmConvertSession.java b/telephony/common/com/google/android/mms/util/DrmConvertSession.java index 156c7ad8baac..17ab15470670 100644 --- a/telephony/common/com/google/android/mms/util/DrmConvertSession.java +++ b/telephony/common/com/google/android/mms/util/DrmConvertSession.java @@ -20,7 +20,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.drm.DrmConvertedStatus; import android.drm.DrmManagerClient; -import android.provider.Downloads; import android.util.Log; import java.io.FileNotFoundException; @@ -33,6 +32,13 @@ public class DrmConvertSession { private int mConvertSessionId; private static final String TAG = "DrmConvertSession"; + // These values are copied from Downloads.Impl.* for backward compatibility since + // {@link #close()} that uses it is marked @UnsupportedAppUsage. + public static final int STATUS_SUCCESS = 200; + public static final int STATUS_NOT_ACCEPTABLE = 406; + public static final int STATUS_UNKNOWN_ERROR = 491; + public static final int STATUS_FILE_ERROR = 492; + private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) { mDrmClient = drmClient; mConvertSessionId = convertSessionId; @@ -118,38 +124,38 @@ public class DrmConvertSession { * Ends a conversion session of a file. * * @param fileName The filename of the converted file. - * @return Downloads.Impl.STATUS_SUCCESS if execution is ok. - * Downloads.Impl.STATUS_FILE_ERROR in case converted file can not - * be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem + * @return STATUS_SUCCESS if execution is ok. + * STATUS_FILE_ERROR in case converted file can not + * be accessed. STATUS_NOT_ACCEPTABLE if a problem * occurs when accessing drm framework. - * Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred. + * STATUS_UNKNOWN_ERROR if a general error occurred. */ @UnsupportedAppUsage public int close(String filename) { DrmConvertedStatus convertedStatus = null; - int result = Downloads.Impl.STATUS_UNKNOWN_ERROR; + int result = STATUS_UNKNOWN_ERROR; if (mDrmClient != null && mConvertSessionId >= 0) { try { convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId); if (convertedStatus == null || convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK || convertedStatus.convertedData == null) { - result = Downloads.Impl.STATUS_NOT_ACCEPTABLE; + result = STATUS_NOT_ACCEPTABLE; } else { RandomAccessFile rndAccessFile = null; try { rndAccessFile = new RandomAccessFile(filename, "rw"); rndAccessFile.seek(convertedStatus.offset); rndAccessFile.write(convertedStatus.convertedData); - result = Downloads.Impl.STATUS_SUCCESS; + result = STATUS_SUCCESS; } catch (FileNotFoundException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; + result = STATUS_FILE_ERROR; Log.w(TAG, "File: " + filename + " could not be found.", e); } catch (IOException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; + result = STATUS_FILE_ERROR; Log.w(TAG, "Could not access File: " + filename + " .", e); } catch (IllegalArgumentException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; + result = STATUS_FILE_ERROR; Log.w(TAG, "Could not open file in mode: rw", e); } catch (SecurityException e) { Log.w(TAG, "Access to File: " + filename + @@ -159,7 +165,7 @@ public class DrmConvertSession { try { rndAccessFile.close(); } catch (IOException e) { - result = Downloads.Impl.STATUS_FILE_ERROR; + result = STATUS_FILE_ERROR; Log.w(TAG, "Failed to close File:" + filename + ".", e); } diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java index db17a9505a27..d2a5905f7a99 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -1,6 +1,7 @@ package android.telephony; import android.annotation.IntDef; +import android.provider.Telephony; import android.telecom.Connection; import android.telephony.data.ApnSetting; @@ -651,4 +652,13 @@ public class Annotation { }) @Retention(RetentionPolicy.SOURCE) public @interface UiccAppType{} + + /** @hide */ + @IntDef({ + Telephony.Carriers.SKIP_464XLAT_DEFAULT, + Telephony.Carriers.SKIP_464XLAT_DISABLE, + Telephony.Carriers.SKIP_464XLAT_ENABLE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Skip464XlatStatus {} } diff --git a/telephony/java/android/telephony/CellBroadcastService.java b/telephony/java/android/telephony/CellBroadcastService.java index 09e22aa4eb24..ac775b391e94 100644 --- a/telephony/java/android/telephony/CellBroadcastService.java +++ b/telephony/java/android/telephony/CellBroadcastService.java @@ -20,6 +20,7 @@ import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.WorkerThread; import android.app.Service; import android.content.Intent; import android.os.Bundle; @@ -102,6 +103,17 @@ public abstract class CellBroadcastService extends Service { @NonNull String originatingAddress, @NonNull Consumer<Bundle> callback); /** + * Get broadcasted area information. + * + * @param slotIndex the index of the slot which received the area information. + * + * @return The area information string sent from the network. This is usually the human readable + * string shown in Setting app's SIM status page. + */ + @WorkerThread + public abstract @NonNull CharSequence getCellBroadcastAreaInfo(int slotIndex); + + /** * If overriding this method, call through to the super method for any unknown actions. * {@inheritDoc} */ @@ -162,5 +174,17 @@ public abstract class CellBroadcastService extends Service { CellBroadcastService.this.onCdmaScpMessage(slotIndex, smsCbProgramData, originatingAddress, consumer); } + + /** + * Get broadcasted area information + * + * @param slotIndex the index of the slot which received the message + * + * @return The area information + */ + @Override + public @NonNull CharSequence getCellBroadcastAreaInfo(int slotIndex) { + return CellBroadcastService.this.getCellBroadcastAreaInfo(slotIndex); + } } } diff --git a/telephony/java/android/telephony/ICellBroadcastService.aidl b/telephony/java/android/telephony/ICellBroadcastService.aidl index 11263d99cb8f..4f20ed67fda4 100644 --- a/telephony/java/android/telephony/ICellBroadcastService.aidl +++ b/telephony/java/android/telephony/ICellBroadcastService.aidl @@ -36,4 +36,7 @@ interface ICellBroadcastService { /** @see android.telephony.CellBroadcastService#onCdmaScpMessage */ oneway void handleCdmaScpMessage(int slotId, in List<CdmaSmsCbProgramData> programData, String originatingAddress, in RemoteCallback callback); + + /** @see android.telephony.CellBroadcastService#getCellBroadcastAreaInfo */ + CharSequence getCellBroadcastAreaInfo(int slotIndex); } diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index 32ffb75f373c..f9de47d7677d 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -25,6 +25,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.AccessNetworkConstants.TransportType; import android.telephony.Annotation.NetworkType; +import android.text.TextUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -214,6 +215,9 @@ public final class NetworkRegistrationInfo implements Parcelable { @Nullable private DataSpecificRegistrationInfo mDataSpecificInfo; + @NonNull + private String mRplmn; + /** * @param domain Network domain. Must be a {@link Domain}. For transport type * {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, this must set to {@link #DOMAIN_PS}. @@ -234,13 +238,14 @@ public final class NetworkRegistrationInfo implements Parcelable { * @param availableServices The list of the supported services. * @param cellIdentity The identity representing a unique cell or wifi AP. Set to null if the * information is not available. + * @param rplmn the registered plmn or the last plmn for attempted registration if reg failed. */ private NetworkRegistrationInfo(@Domain int domain, @TransportType int transportType, @RegistrationState int registrationState, @NetworkType int accessNetworkTechnology, int rejectCause, boolean emergencyOnly, @Nullable @ServiceType List<Integer> availableServices, - @Nullable CellIdentity cellIdentity) { + @Nullable CellIdentity cellIdentity, @Nullable String rplmn) { mDomain = domain; mTransportType = transportType; mRegistrationState = registrationState; @@ -253,6 +258,7 @@ public final class NetworkRegistrationInfo implements Parcelable { mCellIdentity = cellIdentity; mEmergencyOnly = emergencyOnly; mNrState = NR_STATE_NONE; + mRplmn = (rplmn == null) ? "" : rplmn; } /** @@ -263,11 +269,11 @@ public final class NetworkRegistrationInfo implements Parcelable { int registrationState, int accessNetworkTechnology, int rejectCause, boolean emergencyOnly, @Nullable List<Integer> availableServices, - @Nullable CellIdentity cellIdentity, boolean cssSupported, - int roamingIndicator, int systemIsInPrl, + @Nullable CellIdentity cellIdentity, @Nullable String rplmn, + boolean cssSupported, int roamingIndicator, int systemIsInPrl, int defaultRoamingIndicator) { this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause, - emergencyOnly, availableServices, cellIdentity); + emergencyOnly, availableServices, cellIdentity, rplmn); mVoiceSpecificInfo = new VoiceSpecificRegistrationInfo(cssSupported, roamingIndicator, systemIsInPrl, defaultRoamingIndicator); @@ -281,13 +287,13 @@ public final class NetworkRegistrationInfo implements Parcelable { int registrationState, int accessNetworkTechnology, int rejectCause, boolean emergencyOnly, @Nullable List<Integer> availableServices, - @Nullable CellIdentity cellIdentity, int maxDataCalls, - boolean isDcNrRestricted, boolean isNrAvailable, - boolean isEndcAvailable, + @Nullable CellIdentity cellIdentity, @Nullable String rplmn, + int maxDataCalls, boolean isDcNrRestricted, + boolean isNrAvailable, boolean isEndcAvailable, LteVopsSupportInfo lteVopsSupportInfo, boolean isUsingCarrierAggregation) { this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause, - emergencyOnly, availableServices, cellIdentity); + emergencyOnly, availableServices, cellIdentity, rplmn); mDataSpecificInfo = new DataSpecificRegistrationInfo( maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable, lteVopsSupportInfo, isUsingCarrierAggregation); @@ -310,6 +316,7 @@ public final class NetworkRegistrationInfo implements Parcelable { mDataSpecificInfo = source.readParcelable( DataSpecificRegistrationInfo.class.getClassLoader()); mNrState = source.readInt(); + mRplmn = source.readString(); } /** @@ -343,6 +350,7 @@ public final class NetworkRegistrationInfo implements Parcelable { mDataSpecificInfo = new DataSpecificRegistrationInfo(nri.mDataSpecificInfo); } mNrState = nri.mNrState; + mRplmn = nri.mRplmn; } /** @@ -395,6 +403,22 @@ public final class NetworkRegistrationInfo implements Parcelable { } /** + * Get the PLMN-ID for this Network Registration, also known as the RPLMN. + * + * <p>If the device is registered, this will return the registered PLMN-ID. If registration + * has failed, then this will return the PLMN ID of the last attempted registration. If the + * device is not registered, or if is registered to a non-3GPP radio technology, then this + * will return an empty string. + * + * <p>See 3GPP TS 23.122 for further information about the Registered PLMN. + * + * @return the registered PLMN-ID or an empty string. + */ + @NonNull public String getRegisteredPlmn() { + return mRplmn; + } + + /** * @return {@code true} if registered on roaming network, {@code false} otherwise. */ public boolean isRoaming() { @@ -590,6 +614,7 @@ public final class NetworkRegistrationInfo implements Parcelable { .append(" voiceSpecificInfo=").append(mVoiceSpecificInfo) .append(" dataSpecificInfo=").append(mDataSpecificInfo) .append(" nrState=").append(nrStateToString(mNrState)) + .append(" rRplmn=").append(mRplmn) .append("}").toString(); } @@ -597,7 +622,7 @@ public final class NetworkRegistrationInfo implements Parcelable { public int hashCode() { return Objects.hash(mDomain, mTransportType, mRegistrationState, mRoamingType, mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices, - mCellIdentity, mVoiceSpecificInfo, mDataSpecificInfo, mNrState); + mCellIdentity, mVoiceSpecificInfo, mDataSpecificInfo, mNrState, mRplmn); } @Override @@ -620,6 +645,7 @@ public final class NetworkRegistrationInfo implements Parcelable { && Objects.equals(mCellIdentity, other.mCellIdentity) && Objects.equals(mVoiceSpecificInfo, other.mVoiceSpecificInfo) && Objects.equals(mDataSpecificInfo, other.mDataSpecificInfo) + && TextUtils.equals(mRplmn, other.mRplmn) && mNrState == other.mNrState; } @@ -641,6 +667,7 @@ public final class NetworkRegistrationInfo implements Parcelable { dest.writeParcelable(mVoiceSpecificInfo, 0); dest.writeParcelable(mDataSpecificInfo, 0); dest.writeInt(mNrState); + dest.writeString(mRplmn); } /** @@ -741,6 +768,9 @@ public final class NetworkRegistrationInfo implements Parcelable { @Nullable private CellIdentity mCellIdentity; + @NonNull + private String mRplmn = ""; + /** * Default constructor for Builder. */ @@ -855,6 +885,18 @@ public final class NetworkRegistrationInfo implements Parcelable { } /** + * Set the registered PLMN. + * + * @param rplmn the registered plmn. + * + * @return The same instance of the builder. + */ + public @NonNull Builder setRegisteredPlmn(@Nullable String rplmn) { + mRplmn = (rplmn == null) ? "" : rplmn; + return this; + } + + /** * Build the NetworkRegistrationInfo. * @return the NetworkRegistrationInfo object. * @hide @@ -863,7 +905,7 @@ public final class NetworkRegistrationInfo implements Parcelable { public @NonNull NetworkRegistrationInfo build() { return new NetworkRegistrationInfo(mDomain, mTransportType, mRegistrationState, mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices, - mCellIdentity); + mCellIdentity, mRplmn); } } } diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java index 70931fbf2d6d..a53792802d92 100644 --- a/telephony/java/android/telephony/PhoneCapability.java +++ b/telephony/java/android/telephony/PhoneCapability.java @@ -25,7 +25,7 @@ import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.AccessNetworkConstants.RadioAccessNetworkType; import android.telephony.TelephonyManager.NetworkTypeBitMask; -import com.android.internal.util.CollectionUtils; +import com.android.internal.telephony.util.TelephonyUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -181,13 +181,13 @@ public final class PhoneCapability implements Parcelable { this.mEutranUeCategoryUl = eutranUeCategoryUl; this.mPsDataConnectionLingerTimeMillis = psDataConnectionLingerTimeMillis; this.mSupportedRats = supportedRats; - this.mGeranBands = CollectionUtils.emptyIfNull(geranBands); - this.mUtranBands = CollectionUtils.emptyIfNull(utranBands); - this.mEutranBands = CollectionUtils.emptyIfNull(eutranBands); - this.mNgranBands = CollectionUtils.emptyIfNull(ngranBands); - this.mLogicalModemUuids = CollectionUtils.emptyIfNull(logicalModemUuids); - this.mSimSlotCapabilities = CollectionUtils.emptyIfNull(simSlotCapabilities); - this.mConcurrentFeaturesSupport = CollectionUtils.emptyIfNull(concurrentFeaturesSupport); + this.mGeranBands = TelephonyUtils.emptyIfNull(geranBands); + this.mUtranBands = TelephonyUtils.emptyIfNull(utranBands); + this.mEutranBands = TelephonyUtils.emptyIfNull(eutranBands); + this.mNgranBands = TelephonyUtils.emptyIfNull(ngranBands); + this.mLogicalModemUuids = TelephonyUtils.emptyIfNull(logicalModemUuids); + this.mSimSlotCapabilities = TelephonyUtils.emptyIfNull(simSlotCapabilities); + this.mConcurrentFeaturesSupport = TelephonyUtils.emptyIfNull(concurrentFeaturesSupport); } private PhoneCapability(Parcel in) { diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java index 3c6709415281..c0dfec9a1172 100644 --- a/telephony/java/android/telephony/SmsCbMessage.java +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -177,6 +177,9 @@ public final class SmsCbMessage implements Parcelable { @Nullable private final String mLanguage; + /** The 8-bit data coding scheme defined in 3GPP TS 23.038 section 4. */ + private final int mDataCodingScheme; + /** Message body, as a String. */ @Nullable private final String mBody; @@ -220,7 +223,7 @@ public final class SmsCbMessage implements Parcelable { @Nullable SmsCbCmasInfo cmasWarningInfo, int slotIndex, int subId) { this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language, - body, priority, etwsWarningInfo, cmasWarningInfo, 0 /* maximumWaitingTime */, + 0, body, priority, etwsWarningInfo, cmasWarningInfo, 0 /* maximumWaitingTime */, null /* geometries */, System.currentTimeMillis(), slotIndex, subId); } @@ -230,8 +233,8 @@ public final class SmsCbMessage implements Parcelable { */ public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber, @NonNull SmsCbLocation location, int serviceCategory, - @Nullable String language, @Nullable String body, int priority, - @Nullable SmsCbEtwsInfo etwsWarningInfo, + @Nullable String language, int dataCodingScheme, @Nullable String body, + int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo, @Nullable SmsCbCmasInfo cmasWarningInfo, int maximumWaitTimeSec, @Nullable List<Geometry> geometries, long receivedTimeMillis, int slotIndex, int subId) { @@ -241,6 +244,7 @@ public final class SmsCbMessage implements Parcelable { mLocation = location; mServiceCategory = serviceCategory; mLanguage = language; + mDataCodingScheme = dataCodingScheme; mBody = body; mPriority = priority; mEtwsWarningInfo = etwsWarningInfo; @@ -263,6 +267,7 @@ public final class SmsCbMessage implements Parcelable { mLocation = new SmsCbLocation(in); mServiceCategory = in.readInt(); mLanguage = in.readString(); + mDataCodingScheme = in.readInt(); mBody = in.readString(); mPriority = in.readInt(); int type = in.readInt(); @@ -305,6 +310,7 @@ public final class SmsCbMessage implements Parcelable { mLocation.writeToParcel(dest, flags); dest.writeInt(mServiceCategory); dest.writeString(mLanguage); + dest.writeInt(mDataCodingScheme); dest.writeString(mBody); dest.writeInt(mPriority); if (mEtwsWarningInfo != null) { @@ -398,6 +404,15 @@ public final class SmsCbMessage implements Parcelable { } /** + * Get data coding scheme of the message + * + * @return The 8-bit data coding scheme defined in 3GPP TS 23.038 section 4. + */ + public int getDataCodingScheme() { + return mDataCodingScheme; + } + + /** * Get the body of this message, or null if no body available * * @return Body, or null @@ -718,7 +733,7 @@ public final class SmsCbMessage implements Parcelable { cursor.getColumnIndexOrThrow(CellBroadcasts.MAXIMUM_WAIT_TIME)); return new SmsCbMessage(format, geoScope, serialNum, location, category, - language, body, priority, etwsInfo, cmasInfo, maximumWaitTimeSec, geometries, + language, 0, body, priority, etwsInfo, cmasInfo, maximumWaitTimeSec, geometries, receivedTimeMillis, slotIndex, subId); } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 4da3a54306ee..7406c8d5a33c 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -33,7 +33,6 @@ import android.annotation.SuppressAutoDoc; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; -import android.app.BroadcastOptions; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -43,7 +42,6 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; -import android.net.INetworkPolicyManager; import android.net.NetworkCapabilities; import android.net.NetworkPolicyManager; import android.net.Uri; @@ -54,7 +52,6 @@ import android.os.Looper; import android.os.ParcelUuid; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.provider.Telephony.SimInfo; import android.telephony.euicc.EuiccManager; import android.telephony.ims.ImsMmTelManager; @@ -80,7 +77,6 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -882,7 +878,6 @@ public class SubscriptionManager { public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX"; private final Context mContext; - private volatile INetworkPolicyManager mNetworkPolicy; // Cache of Resource that has been created in getResourcesForSubId. Key is a Pair containing // the Context and subId. @@ -974,14 +969,6 @@ public class SubscriptionManager { .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); } - private INetworkPolicyManager getINetworkPolicyManager() { - if (mNetworkPolicy == null) { - mNetworkPolicy = INetworkPolicyManager.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); - } - return mNetworkPolicy; - } - /** * Register for changes to the list of active {@link SubscriptionInfo} records or to the * individual records themselves. When a change occurs the onSubscriptionsChanged method of @@ -2624,15 +2611,6 @@ public class SubscriptionManager { plans.toArray(new SubscriptionPlan[plans.size()]), mContext.getOpPackageName()); } - /** @hide */ - private String getSubscriptionPlansOwner(int subId) { - try { - return getINetworkPolicyManager().getSubscriptionPlansOwner(subId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Temporarily override the billing relationship plan between a carrier and * a specific subscriber to be considered unmetered. This will be reflected @@ -2696,89 +2674,6 @@ public class SubscriptionManager { } /** - * Create an {@link Intent} that can be launched towards the carrier app - * that is currently defining the billing relationship plan through - * {@link #setSubscriptionPlans(int, List)}. - * - * @return ready to launch Intent targeted towards the carrier app, or - * {@code null} if no carrier app is defined, or if the defined - * carrier app provides no management activity. - * @hide - */ - public @Nullable Intent createManageSubscriptionIntent(int subId) { - // Bail if no owner - final String owner = getSubscriptionPlansOwner(subId); - if (owner == null) return null; - - // Bail if no plans - final List<SubscriptionPlan> plans = getSubscriptionPlans(subId); - if (plans.isEmpty()) return null; - - final Intent intent = new Intent(ACTION_MANAGE_SUBSCRIPTION_PLANS); - intent.setPackage(owner); - intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId); - - // Bail if not implemented - if (mContext.getPackageManager().queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) { - return null; - } - - return intent; - } - - /** @hide */ - private @Nullable Intent createRefreshSubscriptionIntent(int subId) { - // Bail if no owner - final String owner = getSubscriptionPlansOwner(subId); - if (owner == null) return null; - - // Bail if no plans - final List<SubscriptionPlan> plans = getSubscriptionPlans(subId); - if (plans.isEmpty()) return null; - - final Intent intent = new Intent(ACTION_REFRESH_SUBSCRIPTION_PLANS); - intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - intent.setPackage(owner); - intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId); - - // Bail if not implemented - if (mContext.getPackageManager().queryBroadcastReceivers(intent, 0).isEmpty()) { - return null; - } - - return intent; - } - - /** - * Check if there is a carrier app that is currently defining the billing - * relationship plan through {@link #setSubscriptionPlans(int, List)} that - * supports refreshing of subscription plans. - * - * @hide - */ - public boolean isSubscriptionPlansRefreshSupported(int subId) { - return createRefreshSubscriptionIntent(subId) != null; - } - - /** - * Request that the carrier app that is currently defining the billing - * relationship plan through {@link #setSubscriptionPlans(int, List)} - * refresh its subscription plans. - * <p> - * If the app is able to successfully update the plans, you'll expect to - * receive the {@link #ACTION_SUBSCRIPTION_PLANS_CHANGED} broadcast. - * - * @hide - */ - public void requestSubscriptionPlansRefresh(int subId) { - final Intent intent = createRefreshSubscriptionIntent(subId); - final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setTemporaryAppWhitelistDuration(TimeUnit.MINUTES.toMillis(1)); - mContext.sendBroadcast(intent, null, options.toBundle()); - } - - /** * Checks whether the app with the given context is authorized to manage the given subscription * according to its metadata. * diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 584b000f8705..26dc5f01e67b 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5640,13 +5640,6 @@ public class TelephonyManager { // /** - * To check the SDK version for {@link TelephonyManager#listen}. - */ - @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P) - private static final long LISTEN_CODE_CHANGE = 147600208L; - - /** * Registers a listener object to receive notification of changes * in specified telephony states. * <p> @@ -5683,19 +5676,7 @@ public class TelephonyManager { (TelephonyRegistryManager) mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); if (telephonyRegistry != null) { - // subId from PhoneStateListener is deprecated Q on forward, use the subId from - // TelephonyManager instance. keep using subId from PhoneStateListener for pre-Q. - int subId = mSubId; - if (Compatibility.isChangeEnabled(LISTEN_CODE_CHANGE)) { - // since mSubId in PhoneStateListener is deprecated from Q on forward, this is - // the only place to set mSubId and its for "informational" only. - // TODO: remove this once we completely get rid of mSubId in PhoneStateListener - listener.mSubId = (events == PhoneStateListener.LISTEN_NONE) - ? SubscriptionManager.INVALID_SUBSCRIPTION_ID : subId; - } else if (listener.mSubId != null) { - subId = listener.mSubId; - } - telephonyRegistry.listenForSubscriber(subId, getOpPackageName(), getFeatureId(), + telephonyRegistry.listenForSubscriber(mSubId, getOpPackageName(), getFeatureId(), listener, events, notifyNow); } else { Rlog.w(TAG, "telephony registry not ready."); @@ -5714,7 +5695,7 @@ public class TelephonyManager { @NonNull public CdmaEriInformation getCdmaEriInformation() { return new CdmaEriInformation( - getCdmaEriIconMode(getSubId()), getCdmaEriIconIndex(getSubId())); + getCdmaEriIconIndex(getSubId()), getCdmaEriIconMode(getSubId())); } /** diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 789632082758..f5dfacc6a0be 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -27,6 +27,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.provider.Telephony; import android.provider.Telephony.Carriers; +import android.telephony.Annotation; import android.telephony.Annotation.ApnType; import android.telephony.Annotation.NetworkType; import android.telephony.ServiceState; @@ -744,7 +745,7 @@ public class ApnSetting implements Parcelable { * @return SKIP_464XLAT_DEFAULT, SKIP_464XLAT_DISABLE or SKIP_464XLAT_ENABLE * @hide */ - @Carriers.Skip464XlatStatus + @Annotation.Skip464XlatStatus public int getSkip464Xlat() { return mSkip464Xlat; } @@ -2061,10 +2062,10 @@ public class ApnSetting implements Parcelable { /** * Sets skip464xlat flag for this APN. * - * @param skip464xlat skip464xlat for this APN + * @param skip464xlat skip464xlat for this APN. * @hide */ - public Builder setSkip464Xlat(@Carriers.Skip464XlatStatus int skip464xlat) { + public Builder setSkip464Xlat(@Annotation.Skip464XlatStatus int skip464xlat) { this.mSkip464Xlat = skip464xlat; return this; } diff --git a/test-mock/src/android/test/mock/MockContentProvider.java b/test-mock/src/android/test/mock/MockContentProvider.java index 85e5916a63df..f7ec11c79476 100644 --- a/test-mock/src/android/test/mock/MockContentProvider.java +++ b/test-mock/src/android/test/mock/MockContentProvider.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.ContentProvider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.IContentProvider; @@ -31,10 +32,12 @@ import android.content.pm.ProviderInfo; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.os.ICancellationSignal; import android.os.ParcelFileDescriptor; +import android.os.RemoteCallback; import android.os.RemoteException; import java.io.FileNotFoundException; @@ -81,6 +84,11 @@ public class MockContentProvider extends ContentProvider { } @Override + public void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException { + MockContentProvider.this.getTypeAsync(uri, callback); + } + + @Override public Uri insert(String callingPackage, @Nullable String featureId, Uri url, ContentValues initialValues, Bundle extras) throws RemoteException { return MockContentProvider.this.insert(url, initialValues, extras); @@ -212,6 +220,18 @@ public class MockContentProvider extends ContentProvider { throw new UnsupportedOperationException("unimplemented mock method"); } + /** + * @hide + */ + @SuppressWarnings("deprecation") + public void getTypeAsync(Uri uri, RemoteCallback remoteCallback) { + AsyncTask.SERIAL_EXECUTOR.execute(() -> { + final Bundle bundle = new Bundle(); + bundle.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri)); + remoteCallback.sendResult(bundle); + }); + } + @Override public Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException("unimplemented mock method"); diff --git a/test-mock/src/android/test/mock/MockIContentProvider.java b/test-mock/src/android/test/mock/MockIContentProvider.java index 464abfb1a514..1831bcdf9df7 100644 --- a/test-mock/src/android/test/mock/MockIContentProvider.java +++ b/test-mock/src/android/test/mock/MockIContentProvider.java @@ -19,16 +19,19 @@ package android.test.mock; import android.annotation.Nullable; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.EntityIterator; import android.content.IContentProvider; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.os.ICancellationSignal; import android.os.ParcelFileDescriptor; +import android.os.RemoteCallback; import android.os.RemoteException; import java.io.FileNotFoundException; @@ -61,6 +64,16 @@ public class MockIContentProvider implements IContentProvider { } @Override + @SuppressWarnings("deprecation") + public void getTypeAsync(Uri uri, RemoteCallback remoteCallback) { + AsyncTask.SERIAL_EXECUTOR.execute(() -> { + final Bundle bundle = new Bundle(); + bundle.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri)); + remoteCallback.sendResult(bundle); + }); + } + + @Override @SuppressWarnings("unused") public Uri insert(String callingPackage, @Nullable String featureId, Uri url, ContentValues initialValues, Bundle extras) throws RemoteException { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java index 42cafd43f8bd..5a66e805c575 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java @@ -66,7 +66,7 @@ public class ChangeAppRotationTest extends FlickerTestBase { @Parameters(name = "{0}-{1}") public static Collection<Object[]> getParams() { int[] supportedRotations = - {Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; + {Surface.ROTATION_0, Surface.ROTATION_90}; Collection<Object[]> params = new ArrayList<>(); for (int begin : supportedRotations) { for (int end : supportedRotations) { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java index fc6719e2f9d9..f740af9b89bf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java @@ -25,6 +25,7 @@ import com.android.server.wm.flicker.helpers.ImeAppHelper; import org.junit.Before; import org.junit.FixMethodOrder; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; @@ -62,6 +63,7 @@ public class CloseImeWindowToHomeTest extends NonRotationTestBase { .forAllEntries()); } + @Ignore("Flaky") @Test public void checkVisibility_imeLayerBecomesInvisible() { checkResults(result -> LayersTraceSubject.assertThat(result) @@ -71,6 +73,7 @@ public class CloseImeWindowToHomeTest extends NonRotationTestBase { .forAllEntries()); } + @Ignore("Flaky") @Test public void checkVisibility_imeAppLayerBecomesInvisible() { checkResults(result -> LayersTraceSubject.assertThat(result) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java index 8559cb9f51f7..37d7c4ca2b46 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java @@ -69,7 +69,7 @@ public class SeamlessAppRotationTest extends FlickerTestBase { @Parameters(name = "{0}") public static Collection<Object[]> getParams() { int[] supportedRotations = - {Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; + {Surface.ROTATION_0, Surface.ROTATION_90}; Collection<Object[]> params = new ArrayList<>(); ArrayList<Intent> testIntents = new ArrayList<>(); @@ -112,7 +112,7 @@ public class SeamlessAppRotationTest extends FlickerTestBase { super.runTransition( changeAppRotation(mIntent, intentId, InstrumentationRegistry.getContext(), - mUiDevice, mBeginRotation, mEndRotation).repeat(5).build()); + mUiDevice, mBeginRotation, mEndRotation).build()); } @Test diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java index 1a68a93eed19..37661828da22 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AlphaLayersActivity.java @@ -50,7 +50,7 @@ public class AlphaLayersActivity extends Activity { setContentView(container); } - + @SuppressWarnings({"UnusedDeclaration"}) static int dipToPx(Context c, int dip) { return (int) (c.getResources().getDisplayMetrics().density * dip + 0.5f); @@ -86,30 +86,24 @@ public class AlphaLayersActivity extends Activity { canvas.save(); canvas.clipRect(20.0f, 0.0f, 40.0f, 20.0f); Log.d(LOG_TAG, "clipRect = " + canvas.getClipBounds()); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(100.0f, 100.0f, 110.0f, 110.0f, - Canvas.EdgeType.BW)); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f, - Canvas.EdgeType.BW)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(100.0f, 100.0f, 110.0f, 110.0f)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f)); canvas.restore(); - + canvas.save(); canvas.scale(2.0f, 2.0f); canvas.clipRect(20.0f, 0.0f, 40.0f, 20.0f); Log.d(LOG_TAG, "clipRect = " + canvas.getClipBounds()); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(50.0f, 50.0f, 60.0f, 60.0f, - Canvas.EdgeType.BW)); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f, - Canvas.EdgeType.BW)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(50.0f, 50.0f, 60.0f, 60.0f)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f)); canvas.restore(); canvas.save(); canvas.translate(20.0f, 20.0f); canvas.clipRect(20.0f, 0.0f, 40.0f, 20.0f); Log.d(LOG_TAG, "clipRect = " + canvas.getClipBounds()); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(80.0f, 80.0f, 90.0f, 90.0f, - Canvas.EdgeType.BW)); - Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f, - Canvas.EdgeType.BW)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(80.0f, 80.0f, 90.0f, 90.0f)); + Log.d(LOG_TAG, "rejected = " + canvas.quickReject(25.0f, 5.0f, 30.0f, 10.0f)); canvas.restore(); canvas.save(); diff --git a/tests/PlatformCompatGating/Android.bp b/tests/PlatformCompatGating/Android.bp index 5e9ef8efc402..609896ea9e95 100644 --- a/tests/PlatformCompatGating/Android.bp +++ b/tests/PlatformCompatGating/Android.bp @@ -18,7 +18,6 @@ android_test { name: "PlatformCompatGating", // Only compile source java files in this apk. srcs: ["src/**/*.java"], - certificate: "platform", libs: [ "android.test.runner", "android.test.base", 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..c00aa2ac25b3 100644 --- a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java +++ b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java @@ -16,7 +16,9 @@ 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; @@ -83,12 +85,16 @@ 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.READ_COMPAT_CHANGE_CONFIG, + Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG); Compatibility.setOverrides(mConfig); try { platformCompat.setOverridesForTest(new CompatibilityChangeConfig(mConfig), @@ -101,6 +107,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/tools/codegen/src/com/android/codegen/ImportsProvider.kt b/tools/codegen/src/com/android/codegen/ImportsProvider.kt index ba0a0318c843..c830aaa0df3d 100644 --- a/tools/codegen/src/com/android/codegen/ImportsProvider.kt +++ b/tools/codegen/src/com/android/codegen/ImportsProvider.kt @@ -46,7 +46,7 @@ interface ImportsProvider { val Parcelling: String get() { return classRef("com.android.internal.util.Parcelling") } val Parcelable: String get() { return classRef("android.os.Parcelable") } val Parcel: String get() { return classRef("android.os.Parcel") } - val UnsupportedAppUsage: String get() { return classRef("android.annotation.UnsupportedAppUsage") } + val UnsupportedAppUsage: String get() { return classRef("android.compat.annotation.UnsupportedAppUsage") } /** * Optionally shortens a class reference if there's a corresponding import present diff --git a/tools/stats_log_api_gen/java_writer_q.cpp b/tools/stats_log_api_gen/java_writer_q.cpp index f8661294821e..1259a68e9624 100644 --- a/tools/stats_log_api_gen/java_writer_q.cpp +++ b/tools/stats_log_api_gen/java_writer_q.cpp @@ -75,9 +75,7 @@ int write_java_methods_q_schema( java_type_name(chainField.javaType), chainField.name.c_str()); } } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { - // Module logging does not yet support key value pair. - fprintf(stderr, "Module logging does not yet support key value pair.\n"); - continue; + fprintf(out, ", android.util.SparseArray<Object> valueMap"); } else { fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); } @@ -161,9 +159,114 @@ int write_java_methods_q_schema( fprintf(out, "%s needed += attrSize;\n", indent.c_str()); break; } + case JAVA_TYPE_KEY_VALUE_PAIR: + { + fprintf(out, + "%s // Calculate bytes needed by Key Value Pairs.\n", + indent.c_str()); + fprintf(out, + "%s final int count = valueMap.size();\n", indent.c_str()); + fprintf(out, + "%s android.util.SparseIntArray intMap = null;\n", indent.c_str()); + fprintf(out, + "%s android.util.SparseLongArray longMap = null;\n", indent.c_str()); + fprintf(out, + "%s android.util.SparseArray<String> stringMap = null;\n", + 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 for (int i = 0; i < count; i++) {\n", indent.c_str()); + fprintf(out, + "%s final int key = valueMap.keyAt(i);\n", indent.c_str()); + fprintf(out, + "%s final Object value = valueMap.valueAt(i);\n", + indent.c_str()); + fprintf(out, + "%s if (value instanceof Integer) {\n", indent.c_str()); + fprintf(out, + "%s keyValuePairSize += LIST_TYPE_OVERHEAD\n", + indent.c_str()); + fprintf(out, + "%s + INT_TYPE_SIZE + INT_TYPE_SIZE;\n", + indent.c_str()); + fprintf(out, + "%s if (null == intMap) {\n", indent.c_str()); + fprintf(out, + "%s intMap = new android.util.SparseIntArray();\n", indent.c_str()); + fprintf(out, + "%s }\n", indent.c_str()); + fprintf(out, + "%s intMap.put(key, (Integer) value);\n", indent.c_str()); + fprintf(out, + "%s } else if (value instanceof Long) {\n", indent.c_str()); + fprintf(out, + "%s keyValuePairSize += LIST_TYPE_OVERHEAD\n", + indent.c_str()); + fprintf(out, + "%s + INT_TYPE_SIZE + LONG_TYPE_SIZE;\n", + indent.c_str()); + fprintf(out, + "%s if (null == longMap) {\n", indent.c_str()); + fprintf(out, + "%s longMap = new android.util.SparseLongArray();\n", indent.c_str()); + fprintf(out, + "%s }\n", indent.c_str()); + fprintf(out, + "%s longMap.put(key, (Long) value);\n", indent.c_str()); + fprintf(out, + "%s } else if (value instanceof String) {\n", indent.c_str()); + fprintf(out, + "%s final String str = (value == null) ? \"\" : " + "(String) value;\n", + indent.c_str()); + fprintf(out, + "%s final int len = " + "str.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;\n", + indent.c_str()); + fprintf(out, + "%s keyValuePairSize += LIST_TYPE_OVERHEAD + INT_TYPE_SIZE\n", + indent.c_str()); + fprintf(out, + "%s + STRING_TYPE_OVERHEAD + len;\n", + indent.c_str()); + fprintf(out, + "%s if (null == stringMap) {\n", indent.c_str()); + fprintf(out, + "%s stringMap = new android.util.SparseArray<>();\n", indent.c_str()); + fprintf(out, + "%s }\n", indent.c_str()); + fprintf(out, + "%s stringMap.put(key, str);\n", indent.c_str()); + fprintf(out, + "%s } else if (value instanceof Float) {\n", indent.c_str()); + fprintf(out, + "%s keyValuePairSize += LIST_TYPE_OVERHEAD\n", + indent.c_str()); + fprintf(out, + "%s + INT_TYPE_SIZE + FLOAT_TYPE_SIZE;\n", + indent.c_str()); + fprintf(out, + "%s if (null == floatMap) {\n", indent.c_str()); + fprintf(out, + "%s floatMap = new android.util.SparseArray<>();\n", indent.c_str()); + fprintf(out, + "%s }\n", indent.c_str()); + fprintf(out, + "%s floatMap.put(key, (Float) value);\n", indent.c_str()); + fprintf(out, + "%s }\n", indent.c_str()); + fprintf(out, + "%s }\n", indent.c_str()); + fprintf(out, "%s needed += keyValuePairSize;\n", indent.c_str()); + break; + } default: - // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIR. - fprintf(stderr, "Module logging does not yet support key value pair.\n"); + // Unsupported types: OBJECT, DOUBLE. + fprintf(stderr, "Module logging does not yet support Object and Double.\n"); return 1; } argIndex++; @@ -253,10 +356,18 @@ int write_java_methods_q_schema( fprintf(out, "%s pos += attrSize;\n", indent.c_str()); break; } + case JAVA_TYPE_KEY_VALUE_PAIR: + 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()); + fprintf(out, "%s pos += keyValuePairSize;\n", indent.c_str()); + break; default: - // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIR. + // Unsupported types: OBJECT, DOUBLE. fprintf(stderr, - "Object, Double, and KeyValuePairs are not supported in module logging"); + "Object and Double are not supported in module logging"); return 1; } argIndex++; @@ -359,6 +470,111 @@ void write_java_helpers_for_q_schema_methods( fprintf(out, "%s}\n", indent.c_str()); fprintf(out, "\n"); } + + if (requiredHelpers & JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS) { + fprintf(out, "%sprivate static void writeKeyValuePairs(byte[] buff, int pos,\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()); + fprintf(out, "%s final android.util.SparseArray<String> stringMap,\n", + indent.c_str()); + fprintf(out, "%s final android.util.SparseArray<Float> floatMap) {\n", + indent.c_str()); + + // 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 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()); + fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); + fprintf(out, "%s final int key = intMap.keyAt(i);\n", indent.c_str()); + fprintf(out, "%s final int value = intMap.valueAt(i);\n", indent.c_str()); + fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str()); + fprintf(out, "%s copyInt(buff, pos + 1, key);\n", indent.c_str()); + fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str()); + fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str()); + fprintf(out, "%s copyInt(buff, pos + 1, value);\n", indent.c_str()); + fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str()); + fprintf(out, "%s }\n", indent.c_str()); + + // 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()); + fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); + fprintf(out, "%s final int key = longMap.keyAt(i);\n", indent.c_str()); + fprintf(out, "%s final long value = longMap.valueAt(i);\n", indent.c_str()); + fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str()); + fprintf(out, "%s copyInt(buff, pos + 1, key);\n", indent.c_str()); + fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str()); + fprintf(out, "%s buff[pos] = LONG_TYPE;\n", indent.c_str()); + fprintf(out, "%s copyLong(buff, pos + 1, value);\n", indent.c_str()); + fprintf(out, "%s pos += LONG_TYPE_SIZE;\n", indent.c_str()); + fprintf(out, "%s }\n", indent.c_str()); + + // 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()); + fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); + fprintf(out, "%s final int key = stringMap.keyAt(i);\n", indent.c_str()); + fprintf(out, "%s final String value = stringMap.valueAt(i);\n", indent.c_str()); + fprintf(out, "%s final byte[] valueBytes = " + "value.getBytes(java.nio.charset.StandardCharsets.UTF_8);\n", + indent.c_str()); + fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str()); + fprintf(out, "%s copyInt(buff, pos + 1, key);\n", indent.c_str()); + fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str()); + fprintf(out, "%s buff[pos] = STRING_TYPE;\n", indent.c_str()); + fprintf(out, "%s copyInt(buff, pos + 1, valueBytes.length);\n", indent.c_str()); + fprintf(out, "%s System.arraycopy(" + "valueBytes, 0, buff, pos + STRING_TYPE_OVERHEAD, valueBytes.length);\n", + indent.c_str()); + fprintf(out, "%s pos += STRING_TYPE_OVERHEAD + valueBytes.length;\n", + indent.c_str()); + fprintf(out, "%s }\n", indent.c_str()); + + // 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()); + fprintf(out, "%s pos += LIST_TYPE_OVERHEAD;\n", indent.c_str()); + fprintf(out, "%s final int key = floatMap.keyAt(i);\n", indent.c_str()); + fprintf(out, "%s final float value = floatMap.valueAt(i);\n", indent.c_str()); + fprintf(out, "%s buff[pos] = INT_TYPE;\n", indent.c_str()); + fprintf(out, "%s copyInt(buff, pos + 1, key);\n", indent.c_str()); + fprintf(out, "%s pos += INT_TYPE_SIZE;\n", indent.c_str()); + fprintf(out, "%s buff[pos] = FLOAT_TYPE;\n", indent.c_str()); + fprintf(out, "%s copyFloat(buff, pos + 1, value);\n", indent.c_str()); + fprintf(out, "%s pos += FLOAT_TYPE_SIZE;\n", indent.c_str()); + fprintf(out, "%s }\n", indent.c_str()); + fprintf(out, "%s}\n", indent.c_str()); + fprintf(out, "\n"); + } } #if defined(STATS_SCHEMA_LEGACY) @@ -382,7 +598,7 @@ static void write_java_method( java_type_name(chainField.javaType), chainField.name.c_str()); } } else if (*arg == JAVA_TYPE_KEY_VALUE_PAIR) { - fprintf(out, ", android.util.SparseArray<Object> value_map"); + fprintf(out, ", android.util.SparseArray<Object> valueMap"); } else { fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); } diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h index 50737a68bf89..cd602e53359a 100644 --- a/tools/stats_log_api_gen/utils.h +++ b/tools/stats_log_api_gen/utils.h @@ -39,6 +39,7 @@ const string DEFAULT_JAVA_CLASS = "StatsLogInternal"; const int JAVA_MODULE_REQUIRES_FLOAT = 0x01; const int JAVA_MODULE_REQUIRES_ATTRIBUTION = 0x02; +const int JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS = 0x04; string make_constant_name(const string& str); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 1d71cf972aec..9a251062cd9e 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1334,10 +1334,12 @@ public class WifiManager { * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration * when auto-connecting to wifi. * <b>Compatibility Note:</b> For applications targeting - * {@link android.os.Build.VERSION_CODES#Q} or above, this API will return an empty list, - * except for: + * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always fail and return an + * empty list. + * <p> + * Deprecation Exemptions: * <ul> - * <li>Device Owner (DO) & Profile Owner (PO) apps will have access to the full list. + * <li>Device Owner (DO), Profile Owner (PO) and system apps will have access to the full list. * <li>Callers with Carrier privilege will receive a restricted list only containing * configurations which they created. * </ul> @@ -1528,7 +1530,13 @@ public class WifiManager { * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration * when auto-connecting to wifi. * <b>Compatibility Note:</b> For applications targeting - * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return {@code -1}. + * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always fail and return + * {@code -1}. + * <p> + * Deprecation Exemptions: + * <ul> + * <li>Device Owner (DO), Profile Owner (PO) and system apps. + * </ul> */ @Deprecated public int addNetwork(WifiConfiguration config) { @@ -1563,7 +1571,13 @@ public class WifiManager { * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration * when auto-connecting to wifi. * <b>Compatibility Note:</b> For applications targeting - * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return {@code -1}. + * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always fail and return + * {@code -1}. + * <p> + * Deprecation Exemptions: + * <ul> + * <li>Device Owner (DO), Profile Owner (PO) and system apps. + * </ul> */ @Deprecated public int updateNetwork(WifiConfiguration config) { @@ -1961,8 +1975,13 @@ public class WifiManager { * See {@link #addNetworkSuggestions(List)}, {@link #removeNetworkSuggestions(List)} for new * API to add Wi-Fi networks for consideration when auto-connecting to wifi. * <b>Compatibility Note:</b> For applications targeting - * {@link android.os.Build.VERSION_CODES#R} or above, except for system of DO/PO apps, this API - * will throw {@link IllegalArgumentException} + * {@link android.os.Build.VERSION_CODES#R} or above, this API will always fail and throw + * {@link IllegalArgumentException}. + * <p> + * Deprecation Exemptions: + * <ul> + * <li>Device Owner (DO), Profile Owner (PO) and system apps. + * </ul> */ public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) { try { @@ -2088,7 +2107,13 @@ public class WifiManager { * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration * when auto-connecting to wifi. * <b>Compatibility Note:</b> For applications targeting - * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false. + * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always fail and return + * {@code false}. + * <p> + * Deprecation Exemptions: + * <ul> + * <li>Device Owner (DO), Profile Owner (PO) and system apps. + * </ul> */ @Deprecated public boolean removeNetwork(int netId) { @@ -2132,7 +2157,12 @@ public class WifiManager { * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration * when auto-connecting to wifi. * <b>Compatibility Note:</b> For applications targeting - * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false. + * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always fail and return + * {@code false}. + * Deprecation Exemptions: + * <ul> + * <li>Device Owner (DO), Profile Owner (PO) and system apps. + * </ul> */ @Deprecated public boolean enableNetwork(int netId, boolean attemptConnect) { @@ -2162,7 +2192,13 @@ public class WifiManager { * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration * when auto-connecting to wifi. * <b>Compatibility Note:</b> For applications targeting - * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false. + * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always fail and return + * {@code false}. + * <p> + * Deprecation Exemptions: + * <ul> + * <li>Device Owner (DO), Profile Owner (PO) and system apps. + * </ul> */ @Deprecated public boolean disableNetwork(int netId) { @@ -2185,7 +2221,13 @@ public class WifiManager { * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration * when auto-connecting to wifi. * <b>Compatibility Note:</b> For applications targeting - * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false. + * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always fail and return + * {@code false}. + * <p> + * Deprecation Exemptions: + * <ul> + * <li>Device Owner (DO), Profile Owner (PO) and system apps. + * </ul> */ @Deprecated public boolean disconnect() { @@ -2209,7 +2251,13 @@ public class WifiManager { * {@link #removeNetworkSuggestions(List)} for new API to add Wi-Fi networks for consideration * when auto-connecting to wifi. * <b>Compatibility Note:</b> For applications targeting - * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return false. + * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always fail and return + * {@code false}. + * <p> + * Deprecation Exemptions: + * <ul> + * <li>Device Owner (DO), Profile Owner (PO) and system apps. + * </ul> */ @Deprecated public boolean reconnect() { @@ -2803,9 +2851,14 @@ public class WifiManager { * @deprecated Starting with Build.VERSION_CODES#Q, applications are not allowed to * enable/disable Wi-Fi. * <b>Compatibility Note:</b> For applications targeting - * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always return {@code false} - * and will have no effect. If apps are targeting an older SDK ( - * {@link android.os.Build.VERSION_CODES#P} or below), they can continue to use this API. + * {@link android.os.Build.VERSION_CODES#Q} or above, this API will always fail and return + * {@code false}. If apps are targeting an older SDK ({@link android.os.Build.VERSION_CODES#P} + * or below), they can continue to use this API. + * <p> + * Deprecation Exemptions: + * <ul> + * <li>Device Owner (DO), Profile Owner (PO) and system apps. + * </ul> */ @Deprecated public boolean setWifiEnabled(boolean enabled) { |