diff options
540 files changed, 12836 insertions, 4941 deletions
diff --git a/Android.bp b/Android.bp index 69d654fbddbc..df852bda4904 100644 --- a/Android.bp +++ b/Android.bp @@ -263,8 +263,6 @@ filegroup { ":libcamera_client_aidl", ":libcamera_client_framework_aidl", ":libupdate_engine_aidl", - // TODO: this needs to be removed when statsd-framework.jar is separated out - ":statsd_java_aidl", ":storaged_aidl", ":vold_aidl", @@ -445,13 +443,6 @@ filegroup { } filegroup { - name: "graphicsstats_proto", - srcs: [ - "libs/hwui/protos/graphicsstats.proto", - ], -} - -filegroup { name: "libvibrator_aidl", srcs: [ "core/java/android/os/IExternalVibrationController.aidl", @@ -467,7 +458,7 @@ java_library { libs: [ "framework-appsearch-stubs", "framework-sdkextensions-stubs-systemapi", - "framework-statsd", // TODO(b/146167933): Use framework-statsd-stubs + "framework-statsd-stubs-module_libs_api", "framework-permission-stubs-systemapi", "framework-wifi-stubs", "ike-stubs", @@ -520,8 +511,7 @@ java_library { "framework-mediaprovider-stubs-systemapi", "framework-permission-stubs-systemapi", "framework-sdkextensions-stubs-systemapi", - // TODO(b/146167933): Use framework-statsd-stubs instead. - "framework-statsd", + "framework-statsd-stubs-module_libs_api", "framework-wifi-stubs", "ike-stubs", "framework-tethering-stubs", @@ -625,6 +615,18 @@ genrule { out: ["com/android/internal/util/FrameworkStatsLog.java"], } +java_library { + name: "uieventloggerlib", + srcs: [ + "core/java/com/android/internal/logging/UiEvent.java", + "core/java/com/android/internal/logging/UiEventLogger.java", + "core/java/com/android/internal/logging/UiEventLoggerImpl.java", + "core/java/com/android/internal/logging/InstanceId.java", + "core/java/com/android/internal/logging/InstanceIdSequence.java", + ":statslog-framework-java-gen", + ], +} + gensrcs { name: "framework-javastream-protos", depfile: true, diff --git a/StubLibraries.bp b/StubLibraries.bp index d4db7373504b..50d23ad2d657 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -190,7 +190,6 @@ droidstubs { droidstubs { name: "module-lib-api", defaults: ["metalava-api-stubs-default"], - libs: ["framework-all"], arg_files: ["core/res/AndroidManifest.xml"], args: metalava_framework_docs_args + module_libs, check_api: { @@ -222,7 +221,6 @@ droidstubs { droidstubs { name: "module-lib-api-stubs-docs", defaults: ["metalava-api-stubs-default"], - libs: ["framework-all"], arg_files: ["core/res/AndroidManifest.xml"], args: metalava_framework_docs_args + priv_apps + module_libs, } @@ -234,6 +232,9 @@ droidstubs { java_defaults { name: "framework-stubs-default", + libs: [ "stub-annotations" ], + static_libs: [ "private-stub-annotations-jar" ], + sdk_version: "core_current", errorprone: { javacflags: [ "-XepDisableAllChecks", @@ -249,61 +250,25 @@ java_defaults { java_library_static { name: "android_stubs_current", - srcs: [ - ":api-stubs-docs", - ], - libs: [ - "stub-annotations", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":api-stubs-docs" ], defaults: ["framework-stubs-default"], - sdk_version: "core_current", } java_library_static { name: "android_system_stubs_current", - srcs: [ - ":system-api-stubs-docs", - ], - libs: [ - "stub-annotations", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":system-api-stubs-docs" ], defaults: ["framework-stubs-default"], - sdk_version: "core_current", } java_library_static { name: "android_test_stubs_current", - srcs: [ - ":test-api-stubs-docs", - ], - libs: [ - "stub-annotations", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":test-api-stubs-docs" ], defaults: ["framework-stubs-default"], - sdk_version: "core_current", } java_library_static { name: "android_module_lib_stubs_current", - srcs: [ - ":module-lib-api-stubs-docs", - ], - libs: [ - "stub-annotations", - "framework-all", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":module-lib-api-stubs-docs" ], defaults: ["framework-stubs-default"], } diff --git a/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java b/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java new file mode 100644 index 000000000000..14282bfbd24e --- /dev/null +++ b/apct-tests/perftests/core/src/android/view/CutoutSpecificationBenchmark.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.PathParser; + +import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class CutoutSpecificationBenchmark { + private static final String TAG = "CutoutSpecificationBenchmark"; + + private static final String BOTTOM_MARKER = "@bottom"; + private static final String DP_MARKER = "@dp"; + private static final String RIGHT_MARKER = "@right"; + private static final String LEFT_MARKER = "@left"; + + private static final String DOUBLE_CUTOUT_SPEC = "M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, 20.0595537175\n" + + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n" + + "L 56.8, 32.0\n" + + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@bottom\n" + + "M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, -20.0595537175\n" + + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n" + + "L 56.8, -32.0\n" + + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@dp"; + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private Context mContext; + private DisplayMetrics mDisplayMetrics; + + /** + * Setup the necessary member field used by test methods. + */ + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + mDisplayMetrics = new DisplayMetrics(); + mContext.getDisplay().getRealMetrics(mDisplayMetrics); + } + + + private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) { + final RectF rectF = new RectF(); + p.computeBounds(rectF, false /* unused */); + rectF.round(inoutRect); + inoutRegion.op(inoutRect, Region.Op.UNION); + } + + private static void oldMethodParsingSpec(String spec, int displayWidth, int displayHeight, + float density) { + Path p = null; + Rect boundTop = null; + Rect boundBottom = null; + Rect safeInset = new Rect(); + String bottomSpec = null; + if (!TextUtils.isEmpty(spec)) { + spec = spec.trim(); + final float offsetX; + if (spec.endsWith(RIGHT_MARKER)) { + offsetX = displayWidth; + spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim(); + } else if (spec.endsWith(LEFT_MARKER)) { + offsetX = 0; + spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim(); + } else { + offsetX = displayWidth / 2f; + } + final boolean inDp = spec.endsWith(DP_MARKER); + if (inDp) { + spec = spec.substring(0, spec.length() - DP_MARKER.length()); + } + + if (spec.contains(BOTTOM_MARKER)) { + String[] splits = spec.split(BOTTOM_MARKER, 2); + spec = splits[0].trim(); + bottomSpec = splits[1].trim(); + } + + final Matrix m = new Matrix(); + final Region r = Region.obtain(); + if (!spec.isEmpty()) { + try { + p = PathParser.createPathFromPathData(spec); + } catch (Throwable e) { + Log.wtf(TAG, "Could not inflate cutout: ", e); + } + + if (p != null) { + if (inDp) { + m.postScale(density, density); + } + m.postTranslate(offsetX, 0); + p.transform(m); + + boundTop = new Rect(); + toRectAndAddToRegion(p, r, boundTop); + safeInset.top = boundTop.bottom; + } + } + + if (bottomSpec != null) { + int bottomInset = 0; + Path bottomPath = null; + try { + bottomPath = PathParser.createPathFromPathData(bottomSpec); + } catch (Throwable e) { + Log.wtf(TAG, "Could not inflate bottom cutout: ", e); + } + + if (bottomPath != null) { + // Keep top transform + m.postTranslate(0, displayHeight); + bottomPath.transform(m); + p.addPath(bottomPath); + boundBottom = new Rect(); + toRectAndAddToRegion(bottomPath, r, boundBottom); + bottomInset = displayHeight - boundBottom.top; + } + safeInset.bottom = bottomInset; + } + } + } + + @Test + public void parseByOldMethodForDoubleCutout() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + oldMethodParsingSpec(DOUBLE_CUTOUT_SPEC, mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels, mDisplayMetrics.density); + } + } + + @Test + public void parseByNewMethodForDoubleCutout() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + new CutoutSpecification.Parser(mDisplayMetrics.density, + mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels) + .parse(DOUBLE_CUTOUT_SPEC); + } + } + + @Test + public void parseLongEdgeCutout() { + final String spec = "M 0,0\n" + + "H 48\n" + + "V 48\n" + + "H -48\n" + + "Z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "H 48\n" + + "V 48\n" + + "H -48\n" + + "Z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "H -48\n" + + "V 48\n" + + "H 48\n" + + "Z\n" + + "@right\n" + + "@dp"; + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + new CutoutSpecification.Parser(mDisplayMetrics.density, + mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec); + } + } + + @Test + public void parseShortEdgeCutout() { + final String spec = "M 0,0\n" + + "H 48\n" + + "V 48\n" + + "H -48\n" + + "Z\n" + + "@bottom\n" + + "M 0,0\n" + + "H 48\n" + + "V -48\n" + + "H -48\n" + + "Z\n" + + "@dp"; + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + new CutoutSpecification.Parser(mDisplayMetrics.density, + mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels).parse(spec); + } + } +} diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java index b6e39e14602a..8633c9613138 100644 --- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java @@ -125,7 +125,9 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { final WindowManager.LayoutParams mParams; final int mWidth; final int mHeight; + final Point mOutSurfaceSize = new Point(); final SurfaceControl mOutSurfaceControl; + final SurfaceControl mOutBlastSurfaceControl = new SurfaceControl(); final IntSupplier mViewVisibility; @@ -150,7 +152,8 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame, mOutContentInsets, mOutVisibleInsets, mOutStableInsets, mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration, - mOutSurfaceControl, mOutInsetsState, new Point(), new SurfaceControl()); + mOutSurfaceControl, mOutInsetsState, mOutSurfaceSize, + mOutBlastSurfaceControl); } } } diff --git a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java index 62e9ba84530c..9e17e940a06b 100644 --- a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java +++ b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java @@ -20,15 +20,19 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import android.app.Activity; import android.app.UiAutomation; +import android.content.Context; import android.content.Intent; +import android.os.BatteryManager; import android.os.ParcelFileDescriptor; import android.perftests.utils.PerfTestActivity; +import android.provider.Settings; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.lifecycle.ActivityLifecycleCallback; import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; import androidx.test.runner.lifecycle.Stage; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -52,18 +56,36 @@ public class WindowManagerPerfTestBase { */ static final File BASE_OUT_PATH = new File("/data/local/CorePerfTests"); + private static int sOriginalStayOnWhilePluggedIn; + @BeforeClass public static void setUpOnce() { + final Context context = getInstrumentation().getContext(); + sOriginalStayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); + // Keep the device awake during testing. + setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_USB); + if (!BASE_OUT_PATH.exists()) { executeShellCommand("mkdir -p " + BASE_OUT_PATH); } // In order to be closer to the real use case. executeShellCommand("input keyevent KEYCODE_WAKEUP"); executeShellCommand("wm dismiss-keyguard"); - getInstrumentation().getContext().startActivity(new Intent(Intent.ACTION_MAIN) + context.startActivity(new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } + @AfterClass + public static void tearDownOnce() { + setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); + } + + private static void setStayOnWhilePluggedIn(int value) { + executeShellCommand(String.format("settings put global %s %d", + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value)); + } + /** * Executes shell command with reading the output. It may also used to block until the current * command is completed. @@ -97,7 +119,7 @@ public class WindowManagerPerfTestBase { */ static class PerfTestActivityRule extends ActivityTestRule<PerfTestActivity> { private final Intent mStartIntent = - new Intent().putExtra(PerfTestActivity.INTENT_EXTRA_KEEP_SCREEN_ON, true); + new Intent(getInstrumentation().getTargetContext(), PerfTestActivity.class); private final LifecycleListener mLifecycleListener = new LifecycleListener(); PerfTestActivityRule() { diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 69f4748548a7..939164edd13e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -494,9 +494,10 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String DEPRECATED_KEY_BG_LOW_JOB_COUNT = "bg_low_job_count"; private static final String DEPRECATED_KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count"; - private static final String KEY_MAX_STANDARD_RESCHEDULE_COUNT + private static final String DEPRECATED_KEY_MAX_STANDARD_RESCHEDULE_COUNT = "max_standard_reschedule_count"; - private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count"; + private static final String DEPRECATED_KEY_MAX_WORK_RESCHEDULE_COUNT = + "max_work_reschedule_count"; private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time"; private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time"; private static final String DEPRECATED_KEY_STANDBY_HEARTBEAT_TIME = @@ -525,8 +526,6 @@ public class JobSchedulerService extends com.android.server.SystemService private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS; private static final float DEFAULT_HEAVY_USE_FACTOR = .9f; private static final float DEFAULT_MODERATE_USE_FACTOR = .5f; - private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE; - private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE; private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f; @@ -640,16 +639,6 @@ public class JobSchedulerService extends com.android.server.SystemService "screen_off_job_concurrency_increase_delay_ms", 30_000); /** - * The maximum number of times we allow a job to have itself rescheduled before - * giving up on it, for standard jobs. - */ - int MAX_STANDARD_RESCHEDULE_COUNT = DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT; - /** - * The maximum number of times we allow a job to have itself rescheduled before - * giving up on it, for jobs that are executing work. - */ - int MAX_WORK_RESCHEDULE_COUNT = DEFAULT_MAX_WORK_RESCHEDULE_COUNT; - /** * The minimum backoff time to allow for linear backoff. */ long MIN_LINEAR_BACKOFF_TIME = DEFAULT_MIN_LINEAR_BACKOFF_TIME; @@ -735,10 +724,6 @@ public class JobSchedulerService extends com.android.server.SystemService SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.parse(mParser); - MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT, - DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT); - MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT, - DEFAULT_MAX_WORK_RESCHEDULE_COUNT); MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME, DEFAULT_MIN_LINEAR_BACKOFF_TIME); MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME, @@ -790,8 +775,6 @@ public class JobSchedulerService extends com.android.server.SystemService SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, ""); - pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println(); - pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println(); pw.printPair(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println(); pw.printPair(KEY_MIN_EXP_BACKOFF_TIME, MIN_EXP_BACKOFF_TIME).println(); pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); @@ -827,8 +810,6 @@ public class JobSchedulerService extends com.android.server.SystemService SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto, ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS); - proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT); - proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT); proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME); proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME); proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); @@ -1390,18 +1371,8 @@ public class JobSchedulerService extends com.android.server.SystemService // Effective standby bucket can change after this in some situations so use // the real bucket so that the job is tracked by the controllers. if (js.getStandbyBucket() == RESTRICTED_INDEX) { - js.addDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW); - js.addDynamicConstraint(JobStatus.CONSTRAINT_CHARGING); - js.addDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY); - js.addDynamicConstraint(JobStatus.CONSTRAINT_IDLE); - mRestrictiveControllers.get(j).startTrackingRestrictedJobLocked(js); } else { - js.removeDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW); - js.removeDynamicConstraint(JobStatus.CONSTRAINT_CHARGING); - js.removeDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY); - js.removeDynamicConstraint(JobStatus.CONSTRAINT_IDLE); - mRestrictiveControllers.get(j).stopTrackingRestrictedJobLocked(js); } } @@ -1738,19 +1709,6 @@ public class JobSchedulerService extends com.android.server.SystemService final int backoffAttempts = failureToReschedule.getNumFailures() + 1; long delayMillis; - if (failureToReschedule.hasWorkLocked()) { - if (backoffAttempts > mConstants.MAX_WORK_RESCHEDULE_COUNT) { - Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #" - + backoffAttempts + " > work limit " - + mConstants.MAX_STANDARD_RESCHEDULE_COUNT); - return null; - } - } else if (backoffAttempts > mConstants.MAX_STANDARD_RESCHEDULE_COUNT) { - Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #" - + backoffAttempts + " > std limit " + mConstants.MAX_STANDARD_RESCHEDULE_COUNT); - return null; - } - switch (job.getBackoffPolicy()) { case JobInfo.BACKOFF_POLICY_LINEAR: { long backoff = initialBackoffMillis; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index f706260edec2..1e89158ca4bb 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -17,6 +17,8 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; +import static com.android.server.job.JobSchedulerService.NEVER_INDEX; +import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.app.AppGlobals; @@ -63,26 +65,36 @@ import java.util.function.Predicate; * @hide */ public final class JobStatus { - static final String TAG = "JobSchedulerService"; + private static final String TAG = "JobScheduler.JobStatus"; static final boolean DEBUG = JobSchedulerService.DEBUG; public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE; public static final long NO_EARLIEST_RUNTIME = 0L; - public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0 - public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2 - public static final int CONSTRAINT_BATTERY_NOT_LOW = - JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1 + static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0 + static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2 + static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1 static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3 static final int CONSTRAINT_TIMING_DELAY = 1<<31; static final int CONSTRAINT_DEADLINE = 1<<30; - public static final int CONSTRAINT_CONNECTIVITY = 1 << 28; + static final int CONSTRAINT_CONNECTIVITY = 1 << 28; static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26; static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint /** + * The additional set of dynamic constraints that must be met if the job's effective bucket is + * {@link JobSchedulerService#RESTRICTED_INDEX}. Connectivity can be ignored if the job doesn't + * need network. + */ + private static final int DYNAMIC_RESTRICTED_CONSTRAINTS = + CONSTRAINT_BATTERY_NOT_LOW + | CONSTRAINT_CHARGING + | CONSTRAINT_CONNECTIVITY + | CONSTRAINT_IDLE; + + /** * The constraints that we want to log to statsd. * * Constraints that can be inferred from other atoms have been excluded to avoid logging too @@ -419,7 +431,11 @@ public final class JobStatus { this.requiredConstraints = requiredConstraints; mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST; mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0; - mReadyDynamicSatisfied = true; + if (standbyBucket == RESTRICTED_INDEX) { + addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); + } else { + mReadyDynamicSatisfied = true; + } mLastSuccessfulRunTime = lastSuccessfulRunTime; mLastFailedRunTime = lastFailedRunTime; @@ -727,6 +743,14 @@ public final class JobStatus { } public void setStandbyBucket(int newBucket) { + if (newBucket == RESTRICTED_INDEX) { + // Adding to the bucket. + addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); + } else if (standbyBucket == RESTRICTED_INDEX) { + // Removing from the RESTRICTED bucket. + removeDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); + } + standbyBucket = newBucket; } @@ -1054,6 +1078,11 @@ public final class JobStatus { if (old == state) { return false; } + if (DEBUG) { + Slog.v(TAG, + "Constraint " + constraint + " is " + (!state ? "NOT " : "") + "satisfied for " + + toShortString()); + } satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0); mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST; mReadyDynamicSatisfied = @@ -1086,38 +1115,40 @@ public final class JobStatus { } /** - * Indicates that this job cannot run without the specified constraint. This is evaluated + * Indicates that this job cannot run without the specified constraints. This is evaluated * separately from the job's explicitly requested constraints and MUST be satisfied before * the job can run if the app doesn't have quota. - * */ - public void addDynamicConstraint(int constraint) { - if (constraint == CONSTRAINT_WITHIN_QUOTA) { + private void addDynamicConstraints(int constraints) { + if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) { + // Quota should never be used as a dynamic constraint. Slog.wtf(TAG, "Tried to set quota as a dynamic constraint"); - return; + constraints &= ~CONSTRAINT_WITHIN_QUOTA; } // Connectivity and content trigger are special since they're only valid to add if the // job has requested network or specific content URIs. Adding these constraints to jobs // that don't need them doesn't make sense. - if ((constraint == CONSTRAINT_CONNECTIVITY && !hasConnectivityConstraint()) - || (constraint == CONSTRAINT_CONTENT_TRIGGER && !hasContentTriggerConstraint())) { - return; + if (!hasConnectivityConstraint()) { + constraints &= ~CONSTRAINT_CONNECTIVITY; + } + if (!hasContentTriggerConstraint()) { + constraints &= ~CONSTRAINT_CONTENT_TRIGGER; } - mDynamicConstraints |= constraint; + mDynamicConstraints |= constraints; mReadyDynamicSatisfied = mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints); } /** - * Removes a dynamic constraint from a job, meaning that the requirement is not required for + * Removes dynamic constraints from a job, meaning that the requirements are not required for * the job to run (if the job itself hasn't requested the constraint. This is separate from * the job's explicitly requested constraints and does not remove those requested constraints. * */ - public void removeDynamicConstraint(int constraint) { - mDynamicConstraints &= ~constraint; + private void removeDynamicConstraints(int constraints) { + mDynamicConstraints &= ~constraints; mReadyDynamicSatisfied = mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints); } @@ -1193,7 +1224,11 @@ public final class JobStatus { private boolean isReady(int satisfiedConstraints) { // Quota and dynamic constraints trump all other constraints. - if (!mReadyWithinQuota && !mReadyDynamicSatisfied) { + // NEVER jobs are not supposed to run at all. Since we're using quota to allow parole + // sessions (exempt from dynamic restrictions), we need the additional check to ensure + // that NEVER jobs don't run. + // TODO: cleanup quota and standby bucket management so we don't need the additional checks + if ((!mReadyWithinQuota && !mReadyDynamicSatisfied) || standbyBucket == NEVER_INDEX) { return false; } // Deadline constraint trumps other constraints besides quota and dynamic (except for diff --git a/apex/permission/testing/Android.bp b/apex/permission/testing/Android.bp index f8978dcd83ee..63bf0a08e956 100644 --- a/apex/permission/testing/Android.bp +++ b/apex/permission/testing/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -apex { +apex_test { name: "test_com.android.permission", visibility: [ "//system/apex/tests", diff --git a/apex/sdkextensions/derive_sdk/derive_sdk.cpp b/apex/sdkextensions/derive_sdk/derive_sdk.cpp index 6fb7ef43416e..900193a6bd0d 100644 --- a/apex/sdkextensions/derive_sdk/derive_sdk.cpp +++ b/apex/sdkextensions/derive_sdk/derive_sdk.cpp @@ -69,7 +69,7 @@ int main(int, char**) { auto itr = std::min_element(versions.begin(), versions.end()); std::string prop_value = itr == versions.end() ? "0" : std::to_string(*itr); - if (!android::base::SetProperty("ro.build.version.extensions.r", prop_value)) { + if (!android::base::SetProperty("build.version.extensions.r", prop_value)) { LOG(ERROR) << "failed to set sdk_info prop"; return EXIT_FAILURE; } diff --git a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java index a8a7effa9b6c..103b53e81db5 100644 --- a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java +++ b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java @@ -38,7 +38,7 @@ public class SdkExtensions { private static final int R_EXTENSION_INT; static { - R_EXTENSION_INT = SystemProperties.getInt("ro.build.version.extensions.r", 0); + R_EXTENSION_INT = SystemProperties.getInt("build.version.extensions.r", 0); } /** diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp index 6d639fddd043..db5f439cd40e 100644 --- a/apex/statsd/aidl/Android.bp +++ b/apex/statsd/aidl/Android.bp @@ -13,35 +13,31 @@ // See the License for the specific language governing permissions and // limitations under the License. // - -// TODO(b/145815909): move StatsDimensionsValue.aidl here filegroup { - name: "statsd_aidl", + name: "statsd_java_aidl", + srcs: ["**/*.aidl"], +} + +aidl_interface { + name: "statsd-aidl", srcs: [ "android/os/IPendingIntentRef.aidl", "android/os/IPullAtomCallback.aidl", "android/os/IPullAtomResultReceiver.aidl", "android/os/IStatsCompanionService.aidl", "android/os/IStatsd.aidl", + "android/os/StatsDimensionsValueParcel.aidl", "android/util/StatsEventParcel.aidl", ], -} - -filegroup { - name: "statsd_java_aidl", - srcs: ["**/*.aidl"], -} - -// This library is currently unused -aidl_interface { - name: "stats-event-parcel-aidl", - srcs: ["android/util/StatsEventParcel.aidl"], backend: { java: { - sdk_version: "28", + enabled: false, // the platform uses statsd_java_aidl }, cpp: { - enabled: false, + enabled: true, + }, + ndk: { + enabled: true, } } } diff --git a/apex/statsd/aidl/android/os/IPendingIntentRef.aidl b/apex/statsd/aidl/android/os/IPendingIntentRef.aidl index 6b9e467a7e15..000a69992a49 100644 --- a/apex/statsd/aidl/android/os/IPendingIntentRef.aidl +++ b/apex/statsd/aidl/android/os/IPendingIntentRef.aidl @@ -16,7 +16,7 @@ package android.os; -import android.os.StatsDimensionsValue; +import android.os.StatsDimensionsValueParcel; /** * Binder interface to hold a PendingIntent for StatsCompanionService. @@ -42,5 +42,5 @@ interface IPendingIntentRef { */ oneway void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, long subscriptionRuleId, in String[] cookies, - in StatsDimensionsValue dimensionsValue); -}
\ No newline at end of file + in StatsDimensionsValueParcel dimensionsValueParcel); +} diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl index 253b2c17423d..10b1e5b26d12 100644 --- a/apex/statsd/aidl/android/os/IStatsd.aidl +++ b/apex/statsd/aidl/android/os/IStatsd.aidl @@ -222,12 +222,6 @@ interface IStatsd { const int FLAG_REQUIRE_LOW_LATENCY_MONITOR = 0x04; /** - * Logs an event for watchdog rollbacks. - */ - oneway void sendWatchdogRollbackOccurredAtom(in int rollbackType, in String packageName, - in long packageVersionCode, in int rollbackReason, in String failingPackageName); - - /** * Returns the most recently registered experiment IDs. */ long[] getRegisteredExperimentIds(); diff --git a/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl b/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl new file mode 100644 index 000000000000..a8685e34dd52 --- /dev/null +++ b/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl @@ -0,0 +1,21 @@ +package android.os; + +/** + * @hide + */ +parcelable StatsDimensionsValueParcel { + /** + * Field equals: + * - atomTag for top level StatsDimensionsValueParcel + * - position in dimension for all other levels + */ + int field; + int valueType; + + String stringValue; + int intValue; + long longValue; + boolean boolValue; + float floatValue; + StatsDimensionsValueParcel[] tupleValue; +} diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index d85ae69b799f..80def475efb9 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -12,12 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. +genrule { + name: "statslog-statsd-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module statsd" + + " --javaPackage com.android.internal.util --javaClass StatsdStatsLog", + out: ["com/android/internal/util/StatsdStatsLog.java"], +} + +java_library_static { + name: "statslog-statsd", + srcs: [ + ":statslog-statsd-java-gen", + ], +} + filegroup { name: "framework-statsd-sources", srcs: [ - "java/**/*.java", + "java/**/*.java", + ":statsd_java_aidl", + ":statslog-statsd-java-gen", ], - path: "java", } java_library { @@ -42,8 +58,7 @@ java_library { hostdex: true, // for hiddenapi check visibility: [ "//frameworks/base/apex/statsd:__subpackages__", - //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs - "//frameworks/base", + //TODO(b/146167933) remove this "//frameworks/opt/net/wifi/service", ], apex_available: [ @@ -103,7 +118,6 @@ java_library { sdk_version: "core_platform", } -// TODO(b/146167933): Use these stubs in frameworks/base/Android.bp java_library { name: "framework-statsd-stubs-systemapi", srcs: [ ":framework-statsd-stubs-srcs-systemapi" ], diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java index 411482b88326..526d17ff0d71 100644 --- a/apex/statsd/framework/java/android/app/StatsManager.java +++ b/apex/statsd/framework/java/android/app/StatsManager.java @@ -32,7 +32,7 @@ import android.os.IStatsd; import android.os.RemoteException; import android.os.StatsFrameworkInitializer; import android.util.AndroidException; -import android.util.Slog; +import android.util.Log; import android.util.StatsEvent; import android.util.StatsEventParcel; @@ -155,7 +155,7 @@ public final class StatsManager { // can throw IllegalArgumentException service.addConfiguration(configKey, config, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when adding configuration"); + Log.e(TAG, "Failed to connect to statsmanager when adding configuration"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -191,7 +191,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); service.removeConfiguration(configKey, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when removing configuration"); + Log.e(TAG, "Failed to connect to statsmanager when removing configuration"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -258,7 +258,7 @@ public final class StatsManager { mContext.getOpPackageName()); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber", + Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber", e); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { @@ -311,7 +311,7 @@ public final class StatsManager { } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when registering data listener."); + Log.e(TAG, "Failed to connect to statsmanager when registering data listener."); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -348,7 +348,7 @@ public final class StatsManager { } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager " + Log.e(TAG, "Failed to connect to statsmanager " + "when registering active configs listener."); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { @@ -387,7 +387,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); return service.getData(configKey, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when getting data"); + Log.e(TAG, "Failed to connect to statsmanager when getting data"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -424,7 +424,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); return service.getMetadata(mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when getting metadata"); + Log.e(TAG, "Failed to connect to statsmanager when getting metadata"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -464,7 +464,7 @@ public final class StatsManager { return service.getRegisteredExperimentIds(); } catch (RemoteException e) { if (DEBUG) { - Slog.d(TAG, + Log.d(TAG, "Failed to connect to StatsManagerService when getting " + "registered experiment IDs"); } @@ -555,7 +555,7 @@ public final class StatsManager { try { resultReceiver.pullFinished(atomTag, success, parcels); } catch (RemoteException e) { - Slog.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId); + Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId); } }); } finally { diff --git a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java index 886130fc5f14..35273da96413 100644 --- a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java +++ b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java @@ -16,7 +16,7 @@ package android.os; import android.annotation.SystemApi; -import android.util.Slog; +import android.util.Log; import java.util.ArrayList; import java.util.List; @@ -96,6 +96,47 @@ public final class StatsDimensionsValue implements Parcelable { } /** + * Creates a {@code StatsDimensionsValue} from a StatsDimensionsValueParcel + * TODO(b/149103391): Make StatsDimensionsValue a wrapper on top of + * StatsDimensionsValueParcel. + * + * @hide + */ + public StatsDimensionsValue(StatsDimensionsValueParcel parcel) { + mField = parcel.field; + mValueType = parcel.valueType; + switch (mValueType) { + case STRING_VALUE_TYPE: + mValue = parcel.stringValue; + break; + case INT_VALUE_TYPE: + mValue = parcel.intValue; + break; + case LONG_VALUE_TYPE: + mValue = parcel.longValue; + break; + case BOOLEAN_VALUE_TYPE: + mValue = parcel.boolValue; + break; + case FLOAT_VALUE_TYPE: + mValue = parcel.floatValue; + break; + case TUPLE_VALUE_TYPE: + StatsDimensionsValue[] values = new StatsDimensionsValue[parcel.tupleValue.length]; + for (int i = 0; i < parcel.tupleValue.length; i++) { + values[i] = new StatsDimensionsValue(parcel.tupleValue[i]); + } + mValue = values; + break; + default: + Log.w(TAG, "StatsDimensionsValueParcel contains bad valueType: " + mValueType); + mValue = null; + break; + } + } + + + /** * Return the field, i.e. the tag of a statsd atom. * * @return the field @@ -114,7 +155,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == STRING_VALUE_TYPE) return (String) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return null; } @@ -128,7 +169,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == INT_VALUE_TYPE) return (Integer) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return 0; } @@ -142,7 +183,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == LONG_VALUE_TYPE) return (Long) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return 0; } @@ -157,7 +198,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return false; } @@ -171,7 +212,7 @@ public final class StatsDimensionsValue implements Parcelable { try { if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return 0; } @@ -197,7 +238,7 @@ public final class StatsDimensionsValue implements Parcelable { } return copy; } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); return null; } } @@ -256,7 +297,7 @@ public final class StatsDimensionsValue implements Parcelable { } return sb.toString(); } catch (ClassCastException e) { - Slog.w(TAG, "Failed to successfully get value", e); + Log.w(TAG, "Failed to successfully get value", e); } return ""; } @@ -316,11 +357,11 @@ public final class StatsDimensionsValue implements Parcelable { return true; } default: - Slog.w(TAG, "readValue of an impossible type " + valueType); + Log.w(TAG, "readValue of an impossible type " + valueType); return false; } } catch (ClassCastException e) { - Slog.w(TAG, "writeValue cast failed", e); + Log.w(TAG, "writeValue cast failed", e); return false; } } @@ -347,7 +388,7 @@ public final class StatsDimensionsValue implements Parcelable { return values; } default: - Slog.w(TAG, "readValue of an impossible type " + valueType); + Log.w(TAG, "readValue of an impossible type " + valueType); return null; } } diff --git a/apex/statsd/framework/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java index 791073794b0e..511bc01f521e 100644 --- a/apex/statsd/framework/java/android/util/StatsLog.java +++ b/apex/statsd/framework/java/android/util/StatsLog.java @@ -26,10 +26,10 @@ import android.annotation.SystemApi; import android.content.Context; import android.os.IStatsd; import android.os.RemoteException; -import android.os.ServiceManager; +import android.os.StatsFrameworkInitializer; import android.util.proto.ProtoOutputStream; -import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.StatsdStatsLog; /** * StatsLog provides an API for developers to send events to statsd. The events can be used to @@ -59,17 +59,17 @@ public final class StatsLog { IStatsd service = getIStatsdLocked(); if (service == null) { if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging start"); + Log.d(TAG, "Failed to find statsd when logging start"); } return false; } service.sendAppBreadcrumbAtom(label, - FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__START); + StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START); return true; } catch (RemoteException e) { sService = null; if (DEBUG) { - Slog.d(TAG, "Failed to connect to statsd when logging start"); + Log.d(TAG, "Failed to connect to statsd when logging start"); } return false; } @@ -88,17 +88,17 @@ public final class StatsLog { IStatsd service = getIStatsdLocked(); if (service == null) { if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging stop"); + Log.d(TAG, "Failed to find statsd when logging stop"); } return false; } service.sendAppBreadcrumbAtom( - label, FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); + label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); return true; } catch (RemoteException e) { sService = null; if (DEBUG) { - Slog.d(TAG, "Failed to connect to statsd when logging stop"); + Log.d(TAG, "Failed to connect to statsd when logging stop"); } return false; } @@ -117,17 +117,17 @@ public final class StatsLog { IStatsd service = getIStatsdLocked(); if (service == null) { if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging event"); + Log.d(TAG, "Failed to find statsd when logging event"); } return false; } service.sendAppBreadcrumbAtom( - label, FrameworkStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); + label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); return true; } catch (RemoteException e) { sService = null; if (DEBUG) { - Slog.d(TAG, "Failed to connect to statsd when logging event"); + Log.d(TAG, "Failed to connect to statsd when logging event"); } return false; } @@ -162,7 +162,7 @@ public final class StatsLog { | EXPERIMENT_IDS_FIELD_ID, id); } - FrameworkStatsLog.write(FrameworkStatsLog.BINARY_PUSH_STATE_CHANGED, + StatsdStatsLog.write(StatsdStatsLog.BINARY_PUSH_STATE_CHANGED, trainName, trainVersionCode, (options & IStatsd.FLAG_REQUIRE_STAGING) > 0, @@ -171,57 +171,19 @@ public final class StatsLog { state, proto.getBytes(), 0, - 0); + 0, + false); return true; } - /** - * Logs an event for watchdog rollbacks. - * - * @param rollbackType state of the rollback. - * @param packageName package name being rolled back. - * @param packageVersionCode version of the package being rolled back. - * @param rollbackReason reason the package is being rolled back. - * @param failingPackageName the package name causing the failure. - * - * @return True if the log request was sent to statsd. - * - * @hide - */ - @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS}) - public static boolean logWatchdogRollbackOccurred(int rollbackType, String packageName, - long packageVersionCode, int rollbackReason, String failingPackageName) { - synchronized (sLogLock) { - try { - IStatsd service = getIStatsdLocked(); - if (service == null) { - if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when logging event"); - } - return false; - } - - service.sendWatchdogRollbackOccurredAtom(rollbackType, packageName, - packageVersionCode, rollbackReason, failingPackageName); - return true; - } catch (RemoteException e) { - sService = null; - if (DEBUG) { - Slog.d(TAG, - "Failed to connect to StatsCompanionService when logging " - + "WatchdogRollbackOccurred"); - } - return false; - } - } - } - - private static IStatsd getIStatsdLocked() throws RemoteException { if (sService != null) { return sService; } - sService = IStatsd.Stub.asInterface(ServiceManager.getService("stats")); + sService = IStatsd.Stub.asInterface(StatsFrameworkInitializer + .getStatsServiceManager() + .getStatsdServiceRegisterer() + .get()); return sService; } diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp index 910384845810..0f8a151eed7c 100644 --- a/apex/statsd/service/Android.bp +++ b/apex/statsd/service/Android.bp @@ -1,17 +1,30 @@ // Statsd Service jar, which will eventually be put in the statsd mainline apex. // service-statsd needs to be added to PRODUCT_UPDATABLE_SYSTEM_SERVER_JARS. // This jar will contain StatsCompanionService + +filegroup { + name: "service-statsd-sources", + srcs: [ + "java/**/*.java", + ], +} + java_library { name: "service-statsd", installable: true, srcs: [ - "java/**/*.java", + ":service-statsd-sources", ], - // TODO: link against the proper stubs (b/146084685). + // TODO(b/146209659): Use system_current instead once framework-statsd compiles against + // system_current. + sdk_version: "core_platform", libs: [ - "framework-minus-apex", - "services.core", + "framework-annotations-lib", + "framework-statsd", + // TODO(b/146758669): Remove this line after nullability annotations are system APIs. + "android_system_stubs_current", + "services-stubs", ], apex_available: [ "com.android.os.statsd", diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java index 4495dc9de71e..c1ba73f03c06 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java @@ -24,7 +24,8 @@ import android.os.Binder; import android.os.IPendingIntentRef; import android.os.Process; import android.os.StatsDimensionsValue; -import android.util.Slog; +import android.os.StatsDimensionsValueParcel; +import android.util.Log; import com.android.server.SystemService; @@ -40,6 +41,9 @@ public class StatsCompanion { private static final int AID_STATSD = 1066; + private static final String STATS_COMPANION_SERVICE = "statscompanion"; + private static final String STATS_MANAGER_SERVICE = "statsmanager"; + static void enforceStatsdCallingUid() { if (Binder.getCallingPid() == Process.myPid()) { return; @@ -68,14 +72,12 @@ public class StatsCompanion { mStatsManagerService.setStatsCompanionService(mStatsCompanionService); try { - publishBinderService(Context.STATS_COMPANION_SERVICE, - mStatsCompanionService); - if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE); - publishBinderService(Context.STATS_MANAGER_SERVICE, - mStatsManagerService); - if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_MANAGER_SERVICE); + publishBinderService(STATS_COMPANION_SERVICE, mStatsCompanionService); + if (DEBUG) Log.d(TAG, "Published " + STATS_COMPANION_SERVICE); + publishBinderService(STATS_MANAGER_SERVICE, mStatsManagerService); + if (DEBUG) Log.d(TAG, "Published " + STATS_MANAGER_SERVICE); } catch (Exception e) { - Slog.e(TAG, "Failed to publishBinderService", e); + Log.e(TAG, "Failed to publishBinderService", e); } } @@ -124,7 +126,7 @@ public class StatsCompanion { try { mPendingIntent.send(mContext, CODE_DATA_BROADCAST, intent, null, null); } catch (PendingIntent.CanceledException e) { - Slog.w(TAG, "Unable to send PendingIntent"); + Log.w(TAG, "Unable to send PendingIntent"); } } @@ -136,17 +138,19 @@ public class StatsCompanion { try { mPendingIntent.send(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null); if (DEBUG) { - Slog.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds)); + Log.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds)); } } catch (PendingIntent.CanceledException e) { - Slog.w(TAG, "Unable to send active configs changed broadcast using PendingIntent"); + Log.w(TAG, "Unable to send active configs changed broadcast using PendingIntent"); } } @Override public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, - long subscriptionRuleId, String[] cookies, StatsDimensionsValue dimensionsValue) { + long subscriptionRuleId, String[] cookies, + StatsDimensionsValueParcel dimensionsValueParcel) { enforceStatsdCallingUid(); + StatsDimensionsValue dimensionsValue = new StatsDimensionsValue(dimensionsValueParcel); Intent intent = new Intent() .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid) @@ -162,7 +166,7 @@ public class StatsCompanion { StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList); if (DEBUG) { - Slog.d(TAG, + Log.d(TAG, String.format( "Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}", configUid, configId, subscriptionId, subscriptionRuleId, @@ -172,7 +176,7 @@ public class StatsCompanion { try { mPendingIntent.send(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null); } catch (PendingIntent.CanceledException e) { - Slog.w(TAG, + Log.w(TAG, "Unable to send using PendingIntent from uid " + configUid + "; presumably it had been cancelled."); } diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index a735cb8f14af..cb167c30e30f 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -40,14 +40,10 @@ import android.os.StatsFrameworkInitializer; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.util.Slog; +import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; -import com.android.internal.os.LooperStats; -import com.android.internal.util.DumpUtils; -import com.android.server.BinderCallsStatsService; -import com.android.server.LocalServices; import libcore.io.IoUtils; @@ -89,6 +85,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public static final int DEATH_THRESHOLD = 10; + // TODO(b/149090705): Implement an alternative to sending broadcast with @hide flag + // FLAG_RECEIVER_INCLUDE_BACKGROUND. Instead of using the flag, find the + // list of registered broadcast receivers and send them directed broadcasts + // to wake them up. See b/147374337. + private static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000; + static final class CompanionHandler extends Handler { CompanionHandler(Looper looper) { super(looper); @@ -126,7 +128,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public void onReceive(Context context, Intent intent) { synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd for UserUpdateReceiver"); + Log.w(TAG, "Could not access statsd for UserUpdateReceiver"); return; } try { @@ -134,14 +136,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { // Needed since the new user basically has a version of every app. informAllUidsLocked(context); } catch (RemoteException e) { - Slog.e(TAG, "Failed to inform statsd latest update of all apps", e); + Log.e(TAG, "Failed to inform statsd latest update of all apps", e); forgetEverythingLocked(); } } } }; mShutdownEventReceiver = new ShutdownEventReceiver(); - if (DEBUG) Slog.d(TAG, "Registered receiver for ACTION_PACKAGE_REPLACED and ADDED."); + if (DEBUG) Log.d(TAG, "Registered receiver for ACTION_PACKAGE_REPLACED and ADDED."); HandlerThread handlerThread = new HandlerThread(TAG); handlerThread.start(); mHandler = new CompanionHandler(handlerThread.getLooper()); @@ -171,21 +173,21 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { PackageManager pm = context.getPackageManager(); final List<UserHandle> users = um.getUserHandles(true); if (DEBUG) { - Slog.d(TAG, "Iterating over " + users.size() + " userHandles."); + Log.d(TAG, "Iterating over " + users.size() + " userHandles."); } ParcelFileDescriptor[] fds; try { fds = ParcelFileDescriptor.createPipe(); } catch (IOException e) { - Slog.e(TAG, "Failed to create a pipe to send uid map data.", e); + Log.e(TAG, "Failed to create a pipe to send uid map data.", e); return; } sStatsd.informAllUidData(fds[0]); try { fds[0].close(); } catch (IOException e) { - Slog.e(TAG, "Failed to close the read side of the pipe.", e); + Log.e(TAG, "Failed to close the read side of the pipe.", e); } final ParcelFileDescriptor writeFd = fds[1]; HandlerThread backgroundThread = new HandlerThread( @@ -239,7 +241,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } output.flush(); if (DEBUG) { - Slog.d(TAG, "Sent data for " + numRecords + " apps"); + Log.d(TAG, "Sent data for " + numRecords + " apps"); } } finally { IoUtils.closeQuietly(fout); @@ -261,10 +263,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { return; // Keep only replacing or normal add and remove. } - if (DEBUG) Slog.d(TAG, "StatsCompanionService noticed an app was updated."); + if (DEBUG) Log.d(TAG, "StatsCompanionService noticed an app was updated."); synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of an app update"); + Log.w(TAG, "Could not access statsd to inform it of an app update"); return; } try { @@ -299,7 +301,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { installer == null ? "" : installer); } } catch (Exception e) { - Slog.w(TAG, "Failed to inform statsd of an app update", e); + Log.w(TAG, "Failed to inform statsd of an app update", e); } } } @@ -308,18 +310,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public final static class AnomalyAlarmListener implements OnAlarmListener { @Override public void onAlarm() { - Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred at time " + Log.i(TAG, "StatsCompanionService believes an anomaly has occurred at time " + System.currentTimeMillis() + "ms."); synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing"); + Log.w(TAG, "Could not access statsd to inform it of anomaly alarm firing"); return; } try { // Two-way call to statsd to retain AlarmManager wakelock sStatsd.informAnomalyAlarmFired(); } catch (RemoteException e) { - Slog.w(TAG, "Failed to inform statsd of anomaly alarm firing", e); + Log.w(TAG, "Failed to inform statsd of anomaly alarm firing", e); } } // AlarmManager releases its own wakelock here. @@ -330,18 +332,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void onAlarm() { if (DEBUG) { - Slog.d(TAG, "Time to poll something."); + Log.d(TAG, "Time to poll something."); } synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing."); + Log.w(TAG, "Could not access statsd to inform it of pulling alarm firing."); return; } try { // Two-way call to statsd to retain AlarmManager wakelock sStatsd.informPollAlarmFired(); } catch (RemoteException e) { - Slog.w(TAG, "Failed to inform statsd of pulling alarm firing.", e); + Log.w(TAG, "Failed to inform statsd of pulling alarm firing.", e); } } } @@ -351,18 +353,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void onAlarm() { if (DEBUG) { - Slog.d(TAG, "Time to trigger periodic alarm."); + Log.d(TAG, "Time to trigger periodic alarm."); } synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of periodic alarm firing."); + Log.w(TAG, "Could not access statsd to inform it of periodic alarm firing."); return; } try { // Two-way call to statsd to retain AlarmManager wakelock sStatsd.informAlarmForSubscriberTriggeringFired(); } catch (RemoteException e) { - Slog.w(TAG, "Failed to inform statsd of periodic alarm firing.", e); + Log.w(TAG, "Failed to inform statsd of periodic alarm firing.", e); } } // AlarmManager releases its own wakelock here. @@ -381,16 +383,16 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { return; } - Slog.i(TAG, "StatsCompanionService noticed a shutdown."); + Log.i(TAG, "StatsCompanionService noticed a shutdown."); synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of a shutdown event."); + Log.w(TAG, "Could not access statsd to inform it of a shutdown event."); return; } try { sStatsd.informDeviceShutdown(); } catch (Exception e) { - Slog.w(TAG, "Failed to inform statsd of a shutdown event.", e); + Log.w(TAG, "Failed to inform statsd of a shutdown event.", e); } } } @@ -399,7 +401,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void setAnomalyAlarm(long timestampMs) { StatsCompanion.enforceStatsdCallingUid(); - if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs); + if (DEBUG) Log.d(TAG, "Setting anomaly alarm for " + timestampMs); final long callingToken = Binder.clearCallingIdentity(); try { // using ELAPSED_REALTIME, not ELAPSED_REALTIME_WAKEUP, so if device is asleep, will @@ -415,7 +417,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void cancelAnomalyAlarm() { StatsCompanion.enforceStatsdCallingUid(); - if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm"); + if (DEBUG) Log.d(TAG, "Cancelling anomaly alarm"); final long callingToken = Binder.clearCallingIdentity(); try { mAlarmManager.cancel(mAnomalyAlarmListener); @@ -428,7 +430,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public void setAlarmForSubscriberTriggering(long timestampMs) { StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, + Log.d(TAG, "Setting periodic alarm in about " + (timestampMs - SystemClock.elapsedRealtime())); } @@ -447,7 +449,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public void cancelAlarmForSubscriberTriggering() { StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "Cancelling periodic alarm"); + Log.d(TAG, "Cancelling periodic alarm"); } final long callingToken = Binder.clearCallingIdentity(); try { @@ -461,7 +463,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public void setPullingAlarm(long nextPullTimeMs) { StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "Setting pulling alarm in about " + Log.d(TAG, "Setting pulling alarm in about " + (nextPullTimeMs - SystemClock.elapsedRealtime())); } final long callingToken = Binder.clearCallingIdentity(); @@ -479,7 +481,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public void cancelPullingAlarm() { StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "Cancelling pulling alarm"); + Log.d(TAG, "Cancelling pulling alarm"); } final long callingToken = Binder.clearCallingIdentity(); try { @@ -493,11 +495,11 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public void statsdReady() { StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "learned that statsdReady"); + Log.d(TAG, "learned that statsdReady"); } sayHiToStatsd(); // tell statsd that we're ready too and link to it mContext.sendBroadcastAsUser(new Intent(StatsManager.ACTION_STATSD_STARTED) - .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND), + .addFlags(FLAG_RECEIVER_INCLUDE_BACKGROUND), UserHandle.SYSTEM, android.Manifest.permission.DUMP); } @@ -509,7 +511,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { try { informAllUidsLocked(mContext); } catch (RemoteException e) { - Slog.e(TAG, "Failed to trigger uid snapshot.", e); + Log.e(TAG, "Failed to trigger uid snapshot.", e); } finally { restoreCallingIdentity(token); } @@ -540,7 +542,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { * Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */ void systemReady() { - if (DEBUG) Slog.d(TAG, "Learned that systemReady"); + if (DEBUG) Log.d(TAG, "Learned that systemReady"); sayHiToStatsd(); } @@ -555,27 +557,27 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void sayHiToStatsd() { synchronized (sStatsdLock) { if (sStatsd != null) { - Slog.e(TAG, "Trying to fetch statsd, but it was already fetched", + Log.e(TAG, "Trying to fetch statsd, but it was already fetched", new IllegalStateException( "sStatsd is not null when being fetched")); return; } sStatsd = fetchStatsdService(); if (sStatsd == null) { - Slog.i(TAG, + Log.i(TAG, "Could not yet find statsd to tell it that StatsCompanion is " + "alive."); return; } mStatsManagerService.statsdReady(sStatsd); - if (DEBUG) Slog.d(TAG, "Saying hi to statsd"); + if (DEBUG) Log.d(TAG, "Saying hi to statsd"); try { sStatsd.statsCompanionReady(); // If the statsCompanionReady two-way binder call returns, link to statsd. try { sStatsd.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); } catch (RemoteException e) { - Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e); + Log.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e); forgetEverythingLocked(); } // Setup broadcast receiver for updates. @@ -605,9 +607,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } finally { restoreCallingIdentity(token); } - Slog.i(TAG, "Told statsd that StatsCompanionService is alive."); + Log.i(TAG, "Told statsd that StatsCompanionService is alive."); } catch (RemoteException e) { - Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e); + Log.e(TAG, "Failed to inform statsd that statscompanion is ready", e); forgetEverythingLocked(); } } @@ -616,7 +618,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private class StatsdDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { - Slog.i(TAG, "Statsd is dead - erase all my knowledge, except pullers"); + Log.i(TAG, "Statsd is dead - erase all my knowledge, except pullers"); synchronized (sStatsdLock) { long now = SystemClock.elapsedRealtime(); for (Long timeMillis : mDeathTimeMillis) { @@ -656,22 +658,15 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { cancelAnomalyAlarm(); cancelPullingAlarm(); - BinderCallsStatsService.Internal binderStats = - LocalServices.getService(BinderCallsStatsService.Internal.class); - if (binderStats != null) { - binderStats.reset(); - } - - LooperStats looperStats = LocalServices.getService(LooperStats.class); - if (looperStats != null) { - looperStats.reset(); - } mStatsManagerService.statsdNotReady(); } @Override protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + return; + } synchronized (sStatsdLock) { writer.println( diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java index c1dc584b89b4..4e4bc40b727f 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java @@ -30,7 +30,7 @@ import android.os.IStatsd; import android.os.Process; import android.os.RemoteException; import android.util.ArrayMap; -import android.util.Slog; +import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -192,7 +192,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.registerPullAtomCallback( callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); + Log.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); } finally { Binder.restoreCallingIdentity(token); } @@ -219,7 +219,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { try { statsd.unregisterPullAtomCallback(callingUid, atomTag); } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); + Log.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); } finally { Binder.restoreCallingIdentity(token); } @@ -243,7 +243,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.setDataFetchOperation(configId, pir, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to setDataFetchOperation with statsd"); + Log.e(TAG, "Failed to setDataFetchOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -264,7 +264,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.removeDataFetchOperation(configId, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to removeDataFetchOperation with statsd"); + Log.e(TAG, "Failed to removeDataFetchOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -287,7 +287,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.setActiveConfigsChangedOperation(pir, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd"); + Log.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -308,7 +308,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.removeActiveConfigsChangedOperation(callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd"); + Log.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -336,7 +336,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { configId, subscriberId, pir, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to setBroadcastSubscriber with statsd"); + Log.e(TAG, "Failed to setBroadcastSubscriber with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -362,7 +362,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.unsetBroadcastSubscriber(configId, subscriberId, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to unsetBroadcastSubscriber with statsd"); + Log.e(TAG, "Failed to unsetBroadcastSubscriber with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -378,7 +378,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.getRegisteredExperimentIds(); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to getRegisteredExperimentIds with statsd"); + Log.e(TAG, "Failed to getRegisteredExperimentIds with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -396,7 +396,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.getMetadata(); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to getMetadata with statsd"); + Log.e(TAG, "Failed to getMetadata with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -415,7 +415,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.getData(key, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to getData with statsd"); + Log.e(TAG, "Failed to getData with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -436,7 +436,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } } catch (RemoteException e) { - Slog.e(TAG, "Failed to addConfiguration with statsd"); + Log.e(TAG, "Failed to addConfiguration with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -457,7 +457,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } } catch (RemoteException e) { - Slog.e(TAG, "Failed to removeConfiguration with statsd"); + Log.e(TAG, "Failed to removeConfiguration with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -522,7 +522,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { try { mLock.wait(STATSD_TIMEOUT_MILLIS); } catch (InterruptedException e) { - Slog.e(TAG, "wait for statsd interrupted"); + Log.e(TAG, "wait for statsd interrupted"); } } return mStatsd; @@ -578,7 +578,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { registerAllActiveConfigsChangedOperations(statsd); registerAllBroadcastSubscribers(statsd); } catch (RemoteException e) { - Slog.e(TAG, "StatsManager failed to (re-)register data with statsd"); + Log.e(TAG, "StatsManager failed to (re-)register data with statsd"); } finally { Binder.restoreCallingIdentity(token); } diff --git a/apex/statsd/testing/Android.bp b/apex/statsd/testing/Android.bp index 22e73015ba39..a9cd0ccb53e8 100644 --- a/apex/statsd/testing/Android.bp +++ b/apex/statsd/testing/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -apex { +apex_test { name: "test_com.android.os.statsd", visibility: [ "//system/apex/tests", diff --git a/api/current.txt b/api/current.txt index e3b557d8387c..a7367e8c4943 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4015,7 +4015,7 @@ package android.app { method public android.util.Size getAppTaskThumbnailSize(); method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks(); method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo(); - method @Nullable public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int); + method @NonNull public java.util.List<android.app.ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String, @IntRange(from=0) int, @IntRange(from=0) int); method public int getLargeMemoryClass(); method public int getLauncherLargeIconDensity(); method public int getLauncherLargeIconSize(); @@ -4555,12 +4555,13 @@ package android.app { method public int getPackageUid(); method public int getPid(); method @NonNull public String getProcessName(); - method public int getPss(); + method public long getPss(); method public int getRealUid(); method public int getReason(); - method public int getRss(); + method public long getRss(); method public int getStatus(); method public long getTimestamp(); + method @NonNull public android.os.UserHandle getUserHandle(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.ApplicationExitInfo> CREATOR; field public static final int REASON_ANR = 6; // 0x6 @@ -12574,6 +12575,7 @@ package android.content.res { method public int getLayoutDirection(); method @NonNull public android.os.LocaleList getLocales(); method public boolean isLayoutSizeAtLeast(int); + method public boolean isNightModeActive(); method public boolean isScreenHdr(); method public boolean isScreenRound(); method public boolean isScreenWideColorGamut(); @@ -30779,7 +30781,6 @@ package android.net.sip { method public void close(); method public void continueCall(int) throws android.net.sip.SipException; method public void endCall() throws android.net.sip.SipException; - method @Nullable public android.net.rtp.AudioGroup getAudioGroup(); method public android.net.sip.SipProfile getLocalProfile(); method public android.net.sip.SipProfile getPeerProfile(); method public int getState(); @@ -30790,7 +30791,6 @@ package android.net.sip { method public void makeCall(android.net.sip.SipProfile, android.net.sip.SipSession, int) throws android.net.sip.SipException; method public void sendDtmf(int); method public void sendDtmf(int, android.os.Message); - method public void setAudioGroup(@NonNull android.net.rtp.AudioGroup); method public void setListener(android.net.sip.SipAudioCall.Listener); method public void setListener(android.net.sip.SipAudioCall.Listener, boolean); method public void setSpeakerMode(boolean); @@ -30839,7 +30839,6 @@ package android.net.sip { method public void close(String) throws android.net.sip.SipException; method public android.net.sip.SipSession createSipSession(android.net.sip.SipProfile, android.net.sip.SipSession.Listener) throws android.net.sip.SipException; method public static String getCallId(android.content.Intent); - method @NonNull public java.util.List<android.net.sip.SipProfile> getListOfProfiles() throws android.net.sip.SipException; method public static String getOfferSessionDescription(android.content.Intent); method public android.net.sip.SipSession getSessionFor(android.content.Intent) throws android.net.sip.SipException; method public static boolean isApiSupported(android.content.Context); @@ -30857,11 +30856,6 @@ package android.net.sip { method public void setRegistrationListener(String, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; method public android.net.sip.SipAudioCall takeAudioCall(android.content.Intent, android.net.sip.SipAudioCall.Listener) throws android.net.sip.SipException; method public void unregister(android.net.sip.SipProfile, android.net.sip.SipRegistrationListener) throws android.net.sip.SipException; - field public static final String ACTION_SIP_CALL_OPTION_CHANGED = "android.net.sip.action.SIP_CALL_OPTION_CHANGED"; - field public static final String ACTION_SIP_INCOMING_CALL = "android.net.sip.action.SIP_INCOMING_CALL"; - field public static final String ACTION_SIP_REMOVE_PROFILE = "android.net.sip.action.SIP_REMOVE_PROFILE"; - field public static final String ACTION_SIP_SERVICE_UP = "android.net.sip.action.SIP_SERVICE_UP"; - field public static final String ACTION_START_SIP = "android.net.sip.action.START_SIP"; field public static final String EXTRA_CALL_ID = "android:sipCallID"; field public static final String EXTRA_OFFER_SD = "android:sipOfferSD"; field public static final int INCOMING_CALL_RESULT_CODE = 101; // 0x65 @@ -30871,7 +30865,6 @@ package android.net.sip { method public int describeContents(); method public String getAuthUserName(); method public boolean getAutoRegistration(); - method public int getCallingUid(); method public String getDisplayName(); method public String getPassword(); method public int getPort(); @@ -40425,6 +40418,7 @@ package android.provider { field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled"; field public static final String EXTRA_BIOMETRIC_MINIMUM_STRENGTH_REQUIRED = "android.provider.extra.BIOMETRIC_MINIMUM_STRENGTH_REQUIRED"; field public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID"; + field public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID"; field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled"; field public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes"; field public static final String EXTRA_EASY_CONNECT_ATTEMPTED_SSID = "android.provider.extra.EASY_CONNECT_ATTEMPTED_SSID"; @@ -47321,7 +47315,7 @@ package android.telephony { method public void onSignalStrengthsChanged(android.telephony.SignalStrength); method public void onUserMobileDataStateChanged(boolean); field public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000 - field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000 + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000 field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000 field public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8 field public static final int LISTEN_CALL_STATE = 32; // 0x20 @@ -47334,7 +47328,7 @@ package android.telephony { field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4 field public static final int LISTEN_NONE = 0; // 0x0 field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000 - field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000 + field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000 field public static final int LISTEN_SERVICE_STATE = 1; // 0x1 field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2 field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100 @@ -60898,7 +60892,7 @@ package android.widget { method public int getGravity(); method public float getHorizontalMargin(); method public float getVerticalMargin(); - method @Deprecated public android.view.View getView(); + method @Deprecated @Nullable public android.view.View getView(); method public int getXOffset(); method public int getYOffset(); method public static android.widget.Toast makeText(android.content.Context, CharSequence, int); diff --git a/api/system-current.txt b/api/system-current.txt index eaa7112d278e..26abc64dfe9c 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -585,10 +585,6 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR; } - public final class ApplicationExitInfo implements android.os.Parcelable { - method @NonNull public android.os.UserHandle getUserHandle(); - } - public class BroadcastOptions { method public static android.app.BroadcastOptions makeBasic(); method @RequiresPermission("android.permission.START_ACTIVITIES_FROM_BACKGROUND") public void setBackgroundActivityStartsAllowed(boolean); @@ -2011,18 +2007,15 @@ package android.content.pm { } public final class InstallationFile implements android.os.Parcelable { - ctor public InstallationFile(@NonNull String, long, @Nullable byte[]); + ctor public InstallationFile(int, @NonNull String, long, @Nullable byte[], @Nullable byte[]); method public int describeContents(); - method public int getFileType(); + method public long getLengthBytes(); + method public int getLocation(); method @Nullable public byte[] getMetadata(); method @NonNull public String getName(); - method public long getSize(); + method @Nullable public byte[] getSignature(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstallationFile> CREATOR; - field public static final int FILE_TYPE_APK = 0; // 0x0 - field public static final int FILE_TYPE_LIB = 1; // 0x1 - field public static final int FILE_TYPE_OBB = 2; // 0x2 - field public static final int FILE_TYPE_UNKNOWN = -1; // 0xffffffff } public final class InstantAppInfo implements android.os.Parcelable { @@ -3866,6 +3859,20 @@ package android.location { method @NonNull public android.location.GnssReflectingPlane.Builder setLongitudeDegrees(@FloatRange(from=-180.0F, to=180.0f) double); } + public final class GnssRequest implements android.os.Parcelable { + method public int describeContents(); + method public boolean isFullTracking(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssRequest> CREATOR; + } + + public static final class GnssRequest.Builder { + ctor public GnssRequest.Builder(); + ctor public GnssRequest.Builder(@NonNull android.location.GnssRequest); + method @NonNull public android.location.GnssRequest build(); + method @NonNull public android.location.GnssRequest.Builder setFullTracking(boolean); + } + public final class GnssSingleSatCorrection implements android.os.Parcelable { method public int describeContents(); method @FloatRange(from=0.0f, fromInclusive=false) public float getCarrierFrequencyHz(); @@ -4139,6 +4146,7 @@ package android.location { method public boolean isProviderEnabledForUser(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@NonNull String); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler); + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.LOCATION_HARDWARE}) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent); @@ -4843,6 +4851,8 @@ package android.media.tv.tuner { public class Tuner implements java.lang.AutoCloseable { ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @NonNull String, int, @Nullable android.media.tv.tuner.Tuner.OnResourceLostListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelScanning(); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelTuning(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void clearOnTuneEventListener(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int connectCiCam(int); @@ -4863,8 +4873,6 @@ package android.media.tv.tuner { method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setLna(boolean); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner); - method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopScan(); - method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopTune(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void updateResourcePriority(int, int); } @@ -5746,7 +5754,6 @@ package android.media.tv.tuner.frontend { method public int getFreqOffset(); method public int getHierarchy(); method @NonNull public boolean[] getLayerErrors(); - method public int getLberCn(); method public int getLnbVoltage(); method public int getMer(); method public int getModulation(); @@ -5758,37 +5765,32 @@ package android.media.tv.tuner.frontend { method public int getSnr(); method public int getSpectralInversion(); method public int getSymbolRate(); - method public int getVberCn(); - method public int getXerCn(); method public boolean isDemodLocked(); method public boolean isEwbs(); method public boolean isLnaOn(); method public boolean isRfLock(); field public static final int FRONTEND_STATUS_TYPE_AGC = 14; // 0xe - field public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = 24; // 0x18 + field public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO = 21; // 0x15 field public static final int FRONTEND_STATUS_TYPE_BER = 2; // 0x2 field public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK = 0; // 0x0 field public static final int FRONTEND_STATUS_TYPE_EWBS = 13; // 0xd field public static final int FRONTEND_STATUS_TYPE_FEC = 8; // 0x8 - field public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = 21; // 0x15 - field public static final int FRONTEND_STATUS_TYPE_HIERARCHY = 22; // 0x16 + field public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET = 18; // 0x12 + field public static final int FRONTEND_STATUS_TYPE_HIERARCHY = 19; // 0x13 field public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR = 16; // 0x10 - field public static final int FRONTEND_STATUS_TYPE_LBER_CN = 18; // 0x12 field public static final int FRONTEND_STATUS_TYPE_LNA = 15; // 0xf field public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE = 11; // 0xb - field public static final int FRONTEND_STATUS_TYPE_MER = 20; // 0x14 + field public static final int FRONTEND_STATUS_TYPE_MER = 17; // 0x11 field public static final int FRONTEND_STATUS_TYPE_MODULATION = 9; // 0x9 field public static final int FRONTEND_STATUS_TYPE_PER = 3; // 0x3 field public static final int FRONTEND_STATUS_TYPE_PLP_ID = 12; // 0xc field public static final int FRONTEND_STATUS_TYPE_PRE_BER = 4; // 0x4 - field public static final int FRONTEND_STATUS_TYPE_RF_LOCK = 23; // 0x17 + field public static final int FRONTEND_STATUS_TYPE_RF_LOCK = 20; // 0x14 field public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY = 5; // 0x5 field public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = 6; // 0x6 field public static final int FRONTEND_STATUS_TYPE_SNR = 1; // 0x1 field public static final int FRONTEND_STATUS_TYPE_SPECTRAL = 10; // 0xa field public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = 7; // 0x7 - field public static final int FRONTEND_STATUS_TYPE_VBER_CN = 17; // 0x11 - field public static final int FRONTEND_STATUS_TYPE_XER_CN = 19; // 0x13 } public static class FrontendStatus.Atsc3PlpInfo { @@ -7210,6 +7212,28 @@ package android.net.netstats.provider { } +package android.net.sip { + + public class SipAudioCall { + method @Nullable public android.net.rtp.AudioGroup getAudioGroup(); + method public void setAudioGroup(@NonNull android.net.rtp.AudioGroup); + } + + public class SipManager { + method @NonNull public java.util.List<android.net.sip.SipProfile> getProfiles() throws android.net.sip.SipException; + field public static final String ACTION_SIP_CALL_OPTION_CHANGED = "android.net.sip.action.SIP_CALL_OPTION_CHANGED"; + field public static final String ACTION_SIP_INCOMING_CALL = "android.net.sip.action.SIP_INCOMING_CALL"; + field public static final String ACTION_SIP_REMOVE_PROFILE = "android.net.sip.action.SIP_REMOVE_PROFILE"; + field public static final String ACTION_SIP_SERVICE_UP = "android.net.sip.action.SIP_SERVICE_UP"; + field public static final String ACTION_START_SIP = "android.net.sip.action.START_SIP"; + } + + public class SipProfile implements java.lang.Cloneable android.os.Parcelable java.io.Serializable { + method public int getCallingUid(); + } + +} + package android.net.util { public final class SocketUtils { @@ -10290,7 +10314,7 @@ package android.service.dataloader { public abstract class DataLoaderService extends android.app.Service { ctor public DataLoaderService(); - method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(); + method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams); } public static interface DataLoaderService.DataLoader { diff --git a/api/test-current.txt b/api/test-current.txt index 826c27546aad..e352cb65156e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -923,6 +923,7 @@ package android.content.pm { field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1 field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_KNOWN_PACKAGES = 4202496; // 0x402000 + field public static final int MODULE_APEX_NAME = 1; // 0x1 field public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services"; field public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared"; } diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 7e069a6b4372..956fd29205cb 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -44,12 +44,8 @@ cc_library_host_shared { cc_defaults { name: "statsd_defaults", - aidl: { - include_dirs: ["frameworks/base/core/java"], - }, srcs: [ - ":statsd_aidl", "src/active_config_list.proto", "src/anomaly/AlarmMonitor.cpp", "src/anomaly/AlarmTracker.cpp", @@ -124,14 +120,14 @@ cc_defaults { "libstatslog", "libstatsmetadata", "libsysutils", + "libutils", ], shared_libs: [ "libbinder", "libincident", "liblog", - "libservices", "libstatssocket", - "libutils", + "statsd-aidl-cpp", ], } diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 5b75b97a0764..6b9d0e4fdac0 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -26,6 +26,86 @@ namespace statsd { using std::string; using std::vector; +// These constants must be kept in sync with those in StatsDimensionsValue.java +const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; +const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; +const static int STATS_DIMENSIONS_VALUE_LONG_TYPE = 4; +// const static int STATS_DIMENSIONS_VALUE_BOOL_TYPE = 5; (commented out because +// unused -- statsd does not correctly support bool types) +const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; +const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; + +/** + * Recursive helper function that populates a parent StatsDimensionsValueParcel + * with children StatsDimensionsValueParcels. + * + * \param dims vector of FieldValues stored by HashableDimensionKey + * \param index positions in dims vector to start reading children from + * \param depth level of parent parcel in the full StatsDimensionsValueParcel + * tree + */ +static void populateStatsDimensionsValueParcelChildren(StatsDimensionsValueParcel &parentParcel, + const vector<FieldValue>& dims, size_t& index, + int depth, int prefix) { + while (index < dims.size()) { + const FieldValue& dim = dims[index]; + int fieldDepth = dim.mField.getDepth(); + int fieldPrefix = dim.mField.getPrefix(depth); + StatsDimensionsValueParcel childParcel; + childParcel.field = dim.mField.getPosAtDepth(depth); + if (depth > 2) { + ALOGE("Depth > 2 not supported by StatsDimensionsValueParcel."); + return; + } + if (depth == fieldDepth && prefix == fieldPrefix) { + switch (dim.mValue.getType()) { + case INT: + childParcel.valueType = STATS_DIMENSIONS_VALUE_INT_TYPE; + childParcel.intValue = dim.mValue.int_value; + break; + case LONG: + childParcel.valueType = STATS_DIMENSIONS_VALUE_LONG_TYPE; + childParcel.longValue = dim.mValue.long_value; + break; + case FLOAT: + childParcel.valueType = STATS_DIMENSIONS_VALUE_FLOAT_TYPE; + childParcel.floatValue = dim.mValue.float_value; + break; + case STRING: + childParcel.valueType = STATS_DIMENSIONS_VALUE_STRING_TYPE; + childParcel.stringValue = String16(dim.mValue.str_value.c_str()); + break; + default: + ALOGE("Encountered FieldValue with unsupported value type."); + break; + } + index++; + parentParcel.tupleValue.push_back(childParcel); + } else if (fieldDepth > depth && fieldPrefix == prefix) { + childParcel.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + populateStatsDimensionsValueParcelChildren(childParcel, dims, index, depth + 1, + dim.mField.getPrefix(depth + 1)); + parentParcel.tupleValue.push_back(childParcel); + } else { + return; + } + } +} + +StatsDimensionsValueParcel HashableDimensionKey::toStatsDimensionsValueParcel() const { + StatsDimensionsValueParcel parcel; + if (mValues.size() == 0) { + return parcel; + } + + parcel.field = mValues[0].mField.getTag(); + parcel.valueType = STATS_DIMENSIONS_VALUE_TUPLE_TYPE; + + size_t index = 0; + populateStatsDimensionsValueParcelChildren(parcel, mValues, index, /*depth=*/0, /*prefix=*/0); + return parcel; +} + android::hash_t hashDimension(const HashableDimensionKey& value) { android::hash_t hash = 0; for (const auto& fieldValue : value.getValues()) { diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h index 654e1358f2a1..4adcf967555e 100644 --- a/cmds/statsd/src/HashableDimensionKey.h +++ b/cmds/statsd/src/HashableDimensionKey.h @@ -16,10 +16,11 @@ #pragma once +#include <android/os/StatsDimensionsValueParcel.h> #include <utils/JenkinsHash.h> #include <vector> -#include "FieldValue.h" #include "android-base/stringprintf.h" +#include "FieldValue.h" #include "logd/LogEvent.h" namespace android { @@ -69,6 +70,8 @@ public: return nullptr; } + StatsDimensionsValueParcel toStatsDimensionsValueParcel() const; + std::string toString() const; bool operator!=(const HashableDimensionKey& that) const; diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index bde15a5cdaae..6e7f08135f1e 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -196,17 +196,27 @@ void StatsLogProcessor::onBinaryPushStateChangedEventLocked(LogEvent* event) { !checkPermissionForIds(kPermissionUsage, pid, uid)) { return; } - status_t err = NO_ERROR, err2 = NO_ERROR, err3 = NO_ERROR, err4 = NO_ERROR; - string trainName = string(event->GetString(1 /*train name field id*/, &err)); - int64_t trainVersionCode = event->GetLong(2 /*train version field id*/, &err2); - int32_t state = int32_t(event->GetLong(6 /*state field id*/, &err3)); + // The Get* functions don't modify the status on success, they only write in + // failure statuses, so we can use one status variable for all calls then + // check if it is no longer NO_ERROR. + status_t err = NO_ERROR; + InstallTrainInfo trainInfo; + trainInfo.trainName = string(event->GetString(1 /*train name field id*/, &err)); + trainInfo.trainVersionCode = event->GetLong(2 /*train version field id*/, &err); + trainInfo.requiresStaging = event->GetBool(3 /*requires staging field id*/, &err); + trainInfo.rollbackEnabled = event->GetBool(4 /*rollback enabled field id*/, &err); + trainInfo.requiresLowLatencyMonitor = + event->GetBool(5 /*requires low latency monitor field id*/, &err); + trainInfo.status = int32_t(event->GetLong(6 /*state field id*/, &err)); #ifdef NEW_ENCODING_SCHEME std::vector<uint8_t> trainExperimentIdBytes = - event->GetStorage(7 /*experiment ids field id*/, &err4); + event->GetStorage(7 /*experiment ids field id*/, &err); #else - string trainExperimentIdString = event->GetString(7 /*experiment ids field id*/, &err4); + string trainExperimentIdString = event->GetString(7 /*experiment ids field id*/, &err); #endif - if (err != NO_ERROR || err2 != NO_ERROR || err3 != NO_ERROR || err4 != NO_ERROR) { + bool is_rollback = event->GetBool(10 /*is rollback field id*/, &err); + + if (err != NO_ERROR) { ALOGE("Failed to parse fields in binary push state changed log event"); return; } @@ -220,83 +230,154 @@ void StatsLogProcessor::onBinaryPushStateChangedEventLocked(LogEvent* event) { ALOGE("Failed to parse experimentids in binary push state changed."); return; } - vector<int64_t> experimentIdVector = {trainExperimentIds.experiment_id().begin(), - trainExperimentIds.experiment_id().end()}; + trainInfo.experimentIds = {trainExperimentIds.experiment_id().begin(), + trainExperimentIds.experiment_id().end()}; + // Update the train info on disk and get any data the logevent is missing. - getAndUpdateTrainInfoOnDisk( - state, &trainVersionCode, &trainName, &experimentIdVector); + getAndUpdateTrainInfoOnDisk(is_rollback, &trainInfo); std::vector<uint8_t> trainExperimentIdProto; - writeExperimentIdsToProto(experimentIdVector, &trainExperimentIdProto); + writeExperimentIdsToProto(trainInfo.experimentIds, &trainExperimentIdProto); int32_t userId = multiuser_get_user_id(uid); - event->updateValue(1 /*train name field id*/, trainName, STRING); - event->updateValue(2 /*train version field id*/, trainVersionCode, LONG); + event->updateValue(2 /*train version field id*/, trainInfo.trainVersionCode, LONG); #ifdef NEW_ENCODING_SCHEME event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STORAGE); #else event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STRING); #endif event->updateValue(8 /*user id field id*/, userId, INT); + + if (is_rollback) { + int bit = trainInfo.requiresStaging ? 1 : 0; + event->updateValue(3 /*requires staging field id*/, bit, INT); + bit = trainInfo.rollbackEnabled ? 1 : 0; + event->updateValue(4 /*rollback enabled field id*/, bit, INT); + bit = trainInfo.requiresLowLatencyMonitor ? 1 : 0; + event->updateValue(5 /*requires low latency monitor field id*/, bit, INT); + } } -void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(int32_t state, - int64_t* trainVersionCode, - string* trainName, - std::vector<int64_t>* experimentIds) { +void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(bool is_rollback, + InstallTrainInfo* trainInfo) { + // If the train name is empty, we don't know which train to attribute the + // event to, so return early. + if (trainInfo->trainName.empty()) { + return; + } bool readTrainInfoSuccess = false; InstallTrainInfo trainInfoOnDisk; - readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk); + readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo->trainName, trainInfoOnDisk); bool resetExperimentIds = false; if (readTrainInfoSuccess) { // Keep the old train version if we received an empty version. - if (*trainVersionCode == -1) { - *trainVersionCode = trainInfoOnDisk.trainVersionCode; - } else if (*trainVersionCode != trainInfoOnDisk.trainVersionCode) { - // Reset experiment ids if we receive a new non-empty train version. - resetExperimentIds = true; - } - - // Keep the old train name if we received an empty train name. - if (trainName->size() == 0) { - *trainName = trainInfoOnDisk.trainName; - } else if (*trainName != trainInfoOnDisk.trainName) { - // Reset experiment ids if we received a new valid train name. + if (trainInfo->trainVersionCode == -1) { + trainInfo->trainVersionCode = trainInfoOnDisk.trainVersionCode; + } else if (trainInfo->trainVersionCode != trainInfoOnDisk.trainVersionCode) { + // Reset experiment ids if we receive a new non-empty train version. resetExperimentIds = true; } // Reset if we received a different experiment id. - if (!experimentIds->empty() && - (trainInfoOnDisk.experimentIds.empty() || - experimentIds->at(0) != trainInfoOnDisk.experimentIds[0])) { + if (!trainInfo->experimentIds.empty() && + (trainInfoOnDisk.experimentIds.empty() || + trainInfo->experimentIds.at(0) != trainInfoOnDisk.experimentIds[0])) { resetExperimentIds = true; } } // Find the right experiment IDs - if (!resetExperimentIds && readTrainInfoSuccess) { - *experimentIds = trainInfoOnDisk.experimentIds; + if ((!resetExperimentIds || is_rollback) && readTrainInfoSuccess) { + trainInfo->experimentIds = trainInfoOnDisk.experimentIds; } - if (!experimentIds->empty()) { - int64_t firstId = experimentIds->at(0); - switch (state) { + if (!trainInfo->experimentIds.empty()) { + int64_t firstId = trainInfo->experimentIds.at(0); + switch (trainInfo->status) { case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS: - experimentIds->push_back(firstId + 1); + trainInfo->experimentIds.push_back(firstId + 1); break; case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED: - experimentIds->push_back(firstId + 2); + trainInfo->experimentIds.push_back(firstId + 2); break; case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS: - experimentIds->push_back(firstId + 3); + trainInfo->experimentIds.push_back(firstId + 3); break; } } - StorageManager::writeTrainInfo(*trainVersionCode, *trainName, state, *experimentIds); + if (is_rollback) { + trainInfo->requiresStaging = trainInfoOnDisk.requiresStaging; + trainInfo->rollbackEnabled = trainInfoOnDisk.rollbackEnabled; + trainInfo->requiresLowLatencyMonitor = trainInfoOnDisk.requiresLowLatencyMonitor; + } + + StorageManager::writeTrainInfo(*trainInfo); } +void StatsLogProcessor::onWatchdogRollbackOccurredLocked(LogEvent* event) { + pid_t pid = event->GetPid(); + uid_t uid = event->GetUid(); + if (!checkPermissionForIds(kPermissionDump, pid, uid) || + !checkPermissionForIds(kPermissionUsage, pid, uid)) { + return; + } + // The Get* functions don't modify the status on success, they only write in + // failure statuses, so we can use one status variable for all calls then + // check if it is no longer NO_ERROR. + status_t err = NO_ERROR; + int32_t rollbackType = int32_t(event->GetInt(1 /*rollback type field id*/, &err)); + string packageName = string(event->GetString(2 /*package name field id*/, &err)); + + if (err != NO_ERROR) { + ALOGE("Failed to parse fields in watchdog rollback occurred log event"); + return; + } + + vector<int64_t> experimentIds = + processWatchdogRollbackOccurred(rollbackType, packageName); + vector<uint8_t> experimentIdProto; + writeExperimentIdsToProto(experimentIds, &experimentIdProto); + +#ifdef NEW_ENCODING_SCHEME + event->updateValue(6 /*experiment ids field id*/, experimentIdProto, STORAGE); +#else + event->updateValue(6 /*experiment ids field id*/, experimentIdProto, STRING); +#endif +} + +vector<int64_t> StatsLogProcessor::processWatchdogRollbackOccurred(const int32_t rollbackTypeIn, + const string& packageNameIn) { + // If the package name is empty, we can't attribute it to any train, so + // return early. + if (packageNameIn.empty()) { + return vector<int64_t>(); + } + bool readTrainInfoSuccess = false; + InstallTrainInfo trainInfoOnDisk; + readTrainInfoSuccess = StorageManager::readTrainInfo(packageNameIn, trainInfoOnDisk); + + if (!readTrainInfoSuccess) { + return vector<int64_t>(); + } + + if (trainInfoOnDisk.experimentIds.empty()) { + return vector<int64_t>(); + } + switch (rollbackTypeIn) { + case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: + trainInfoOnDisk.experimentIds.push_back(trainInfoOnDisk.experimentIds[0] + 4); + StorageManager::writeTrainInfo(trainInfoOnDisk); + break; + case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: + trainInfoOnDisk.experimentIds.push_back(trainInfoOnDisk.experimentIds[0] + 5); + StorageManager::writeTrainInfo(trainInfoOnDisk); + break; + } + + return trainInfoOnDisk.experimentIds; +} void StatsLogProcessor::resetConfigs() { std::lock_guard<std::mutex> lock(mMetricsMutex); @@ -321,7 +402,13 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { // Hard-coded logic to update train info on disk and fill in any information // this log event may be missing. if (event->GetTagId() == android::util::BINARY_PUSH_STATE_CHANGED) { - onBinaryPushStateChangedEventLocked(event); + onBinaryPushStateChangedEventLocked(event); + } + + // Hard-coded logic to update experiment ids on disk for certain rollback + // types and fill the rollback atom with experiment ids + if (event->GetTagId() == android::util::WATCHDOG_ROLLBACK_OCCURRED) { + onWatchdogRollbackOccurredLocked(event); } #ifdef VERY_VERBOSE_PRINTING diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index c49f2e0ec68d..42e56760fb89 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -18,6 +18,7 @@ #include <gtest/gtest_prod.h> #include "config/ConfigListener.h" +#include "logd/LogEvent.h" #include "metrics/MetricsManager.h" #include "packages/UidMap.h" #include "external/StatsPullerManager.h" @@ -199,10 +200,18 @@ private: // Handler over the binary push state changed event. void onBinaryPushStateChangedEventLocked(LogEvent* event); + // Handler over the watchdog rollback occurred event. + void onWatchdogRollbackOccurredLocked(LogEvent* event); + // Updates train info on disk based on binary push state changed info and // write disk info into parameters. - void getAndUpdateTrainInfoOnDisk(int32_t state, int64_t* trainVersionCode, - string* trainName, std::vector<int64_t>* experimentIds); + void getAndUpdateTrainInfoOnDisk(bool is_rollback, InstallTrainInfo* trainInfoIn); + + // Gets experiment ids on disk for associated train and updates them + // depending on rollback type. Then writes them back to disk and returns + // them. + std::vector<int64_t> processWatchdogRollbackOccurred(const int32_t rollbackTypeIn, + const string& packageName); // Reset all configs. void resetConfigsLocked(const int64_t timestampNs); diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index a06e59c8e409..168833fc8c8b 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -1293,81 +1293,24 @@ Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) { return Status::ok(); } -Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackTypeIn, - const android::String16& packageNameIn, - const int64_t packageVersionCodeIn, - const int32_t rollbackReasonIn, - const android::String16& - failingPackageNameIn) { - // Note: We skip the usage stats op check here since we do not have a package name. - // This is ok since we are overloading the usage_stats permission. - // This method only sends data, it does not receive it. - pid_t pid = IPCThreadState::self()->getCallingPid(); - uid_t uid = IPCThreadState::self()->getCallingUid(); - // Root, system, and shell always have access - if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) { - // Caller must be granted these permissions - if (!checkPermission(kPermissionDump)) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionDump)); - } - if (!checkPermission(kPermissionUsage)) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, - kPermissionUsage)); - } - } - - android::util::stats_write(android::util::WATCHDOG_ROLLBACK_OCCURRED, - rollbackTypeIn, String8(packageNameIn).string(), packageVersionCodeIn, - rollbackReasonIn, String8(failingPackageNameIn).string()); - - // Fast return to save disk read. - if (rollbackTypeIn != android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS - && rollbackTypeIn != - android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE) { - return Status::ok(); - } - - bool readTrainInfoSuccess = false; - InstallTrainInfo trainInfoOnDisk; - readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk); - - if (!readTrainInfoSuccess) { - return Status::ok(); - } - std::vector<int64_t> experimentIds = trainInfoOnDisk.experimentIds; - if (experimentIds.empty()) { - return Status::ok(); - } - switch (rollbackTypeIn) { - case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE: - experimentIds.push_back(experimentIds[0] + 4); - break; - case android::util::WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS: - experimentIds.push_back(experimentIds[0] + 5); - break; - } - StorageManager::writeTrainInfo(trainInfoOnDisk.trainVersionCode, trainInfoOnDisk.trainName, - trainInfoOnDisk.status, experimentIds); - return Status::ok(); -} - Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) { ENFORCE_UID(AID_SYSTEM); // TODO: add verifier permission + experimentIdsOut->clear(); // Read the latest train info - InstallTrainInfo trainInfo; - if (!StorageManager::readTrainInfo(trainInfo)) { + vector<InstallTrainInfo> trainInfoList = StorageManager::readAllTrainInfo(); + if (trainInfoList.empty()) { // No train info means no experiment IDs, return an empty list - experimentIdsOut->clear(); return Status::ok(); } // Copy the experiment IDs to the out vector - experimentIdsOut->assign(trainInfo.experimentIds.begin(), trainInfo.experimentIds.end()); + for (InstallTrainInfo& trainInfo : trainInfoList) { + experimentIdsOut->insert(experimentIdsOut->end(), + trainInfo.experimentIds.begin(), + trainInfo.experimentIds.end()); + } return Status::ok(); } diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 82a5a5305df4..0527d435fdf4 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -187,16 +187,6 @@ public: virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override; /** - * Binder call to log WatchdogRollbackOccurred atom. - */ - virtual Status sendWatchdogRollbackOccurredAtom( - const int32_t rollbackTypeIn, - const android::String16& packageNameIn, - const int64_t packageVersionCodeIn, - const int32_t rollbackReasonIn, - const android::String16& failingPackageNameIn) override; - - /** * Binder call to get registered experiment IDs. */ virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut); diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 71afc32686f1..89b1798587f0 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -127,7 +127,7 @@ message Atom { WallClockTimeShifted wall_clock_time_shifted = 45 [(module) = "framework"]; AnomalyDetected anomaly_detected = 46; AppBreadcrumbReported app_breadcrumb_reported = - 47 [(allow_from_any_uid) = true, (module) = "framework"]; + 47 [(allow_from_any_uid) = true, (module) = "statsd"]; AppStartOccurred app_start_occurred = 48 [(module) = "framework"]; AppStartCanceled app_start_canceled = 49 [(module) = "framework"]; AppStartFullyDrawn app_start_fully_drawn = 50 [(module) = "framework"]; @@ -188,7 +188,7 @@ message Atom { ServiceStateChanged service_state_changed = 99 [(module) = "framework"]; ServiceLaunchReported service_launch_reported = 100 [(module) = "framework"]; FlagFlipUpdateOccurred flag_flip_update_occurred = 101 [(module) = "framework"]; - BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "framework"]; + BinaryPushStateChanged binary_push_state_changed = 102 [(module) = "statsd"]; DevicePolicyEvent device_policy_event = 103 [(module) = "framework"]; DocsUIFileOperationCanceledReported docs_ui_file_op_canceled = 104 [(module) = "docsui"]; DocsUIFileOperationCopyMoveModeReported docs_ui_file_op_copy_move_mode_reported = @@ -391,6 +391,7 @@ message Atom { WifiHealthStatReported wifi_health_stat_reported = 251 [(module) = "wifi"]; WifiFailureStatReported wifi_failure_stat_reported = 252 [(module) = "wifi"]; WifiConnectionResultReported wifi_connection_result_reported = 253 [(module) = "wifi"]; + AppFreezeChanged app_freeze_changed = 254 [(module) = "framework"]; SdkExtensionStatus sdk_extension_status = 354; } @@ -1874,6 +1875,8 @@ message WatchdogRollbackOccurred { // Set by RollbackPackageHealthObserver to be the package that is failing when a rollback // is initiated. Empty if the package is unknown. optional string failing_package_name = 5; + + optional TrainExperimentIds experiment_ids = 6 [(log_mode) = MODE_BYTES]; } /** @@ -3770,6 +3773,8 @@ message BinaryPushStateChanged { // user id optional int32 user_id = 8; optional int32 reason = 9; + // Whether or not this is a rollback event + optional bool is_rollback = 10; } /* Test atom, is not logged anywhere */ @@ -7514,6 +7519,9 @@ message GrantPermissionsActivityButtonActions { // Button clicked by user - same as bit flags in buttons_presented with only single bit set optional int32 button_clicked = 5; + + // id which identifies single session of user interacting with permission controller + optional int64 session_id = 6; } /** @@ -8362,3 +8370,28 @@ message SdkExtensionStatus { // "Failed" here can mean a symbol that wasn't meant to be visible was, or the other way around. optional int32 failed_call_symbol = 3; } + +/** + * Logs when an app is frozen or unfrozen. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java + */ +message AppFreezeChanged { + // The type of event. + enum Action { + UNKNOWN = 0; + FREEZE_APP = 1; + UNFREEZE_APP = 2; + } + optional Action action = 1; + + // Pid of the process being frozen. + optional int32 pid = 2; + + // Name of the process being frozen. + optional string process_name = 3; + + // Time since last unfrozen. + optional int64 time_unfrozen_millis = 4; +} diff --git a/cmds/statsd/src/external/TrainInfoPuller.cpp b/cmds/statsd/src/external/TrainInfoPuller.cpp index 9d0924297912..a7d8d4ebebae 100644 --- a/cmds/statsd/src/external/TrainInfoPuller.cpp +++ b/cmds/statsd/src/external/TrainInfoPuller.cpp @@ -37,14 +37,16 @@ TrainInfoPuller::TrainInfoPuller() : } bool TrainInfoPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { - InstallTrainInfo trainInfo; - bool ret = StorageManager::readTrainInfo(trainInfo); - if (!ret) { - ALOGW("Failed to read train info."); - return false; + vector<InstallTrainInfo> trainInfoList = + StorageManager::readAllTrainInfo(); + if (trainInfoList.empty()) { + ALOGW("Train info was empty."); + return true; + } + for (InstallTrainInfo& trainInfo : trainInfoList) { + auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo); + data->push_back(event); } - auto event = make_shared<LogEvent>(getWallClockNs(), getElapsedRealtimeNs(), trainInfo); - data->push_back(event); return true; } diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 5509c093a6f6..a67bef451be7 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -56,6 +56,9 @@ struct InstallTrainInfo { std::string trainName; int32_t status; std::vector<int64_t> experimentIds; + bool requiresStaging; + bool rollbackEnabled; + bool requiresLowLatencyMonitor; }; /** diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 507297c6c401..1bac19ed2c5d 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -44,7 +44,7 @@ using std::map; #define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin" // Magic word at the start of the train info file, change this if changing the file format -const uint32_t TRAIN_INFO_FILE_MAGIC = 0xff7447ff; +const uint32_t TRAIN_INFO_FILE_MAGIC = 0xfb7447bf; // for ConfigMetricsReportList const int FIELD_ID_REPORTS = 2; @@ -75,6 +75,29 @@ string StorageManager::getDataHistoryFileName(long wallClockSec, int uid, int64_ (long long)id); } +static const char* findTrainInfoFileNameLocked(const string& trainName) { + unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir); + if (dir == NULL) { + VLOG("Path %s does not exist", TRAIN_INFO_DIR); + return nullptr; + } + dirent* de; + while ((de = readdir(dir.get()))) { + char* fileName = de->d_name; + if (fileName[0] == '.') continue; + + size_t fileNameLength = strlen(fileName); + if (fileNameLength >= trainName.length()) { + if (0 == strncmp(fileName + fileNameLength - trainName.length(), trainName.c_str(), + trainName.length())) { + return fileName; + } + } + } + + return nullptr; +} + // Returns array of int64_t which contains timestamp in seconds, uid, // configID and whether the file is a local history file. static void parseFileName(char* name, FileName* output) { @@ -123,20 +146,25 @@ void StorageManager::writeFile(const char* file, const void* buffer, int numByte close(fd); } -bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, - int32_t status, const std::vector<int64_t>& experimentIds) { +bool StorageManager::writeTrainInfo(const InstallTrainInfo& trainInfo) { std::lock_guard<std::mutex> lock(sTrainInfoMutex); - deleteAllFiles(TRAIN_INFO_DIR); + if (trainInfo.trainName.empty()) { + return false; + } + deleteSuffixedFiles(TRAIN_INFO_DIR, trainInfo.trainName.c_str()); - int fd = open(TRAIN_INFO_PATH, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + std::string fileName = + StringPrintf("%s/%ld_%s", TRAIN_INFO_DIR, (long) getWallClockSec(), + trainInfo.trainName.c_str()); + + int fd = open(fileName.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd == -1) { - VLOG("Attempt to access %s but failed", TRAIN_INFO_PATH); + VLOG("Attempt to access %s but failed", fileName.c_str()); return false; } size_t result; - // Write the magic word result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC)); if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) { @@ -146,8 +174,8 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write the train version - const size_t trainVersionCodeByteCount = sizeof(trainVersionCode); - result = write(fd, &trainVersionCode, trainVersionCodeByteCount); + const size_t trainVersionCodeByteCount = sizeof(trainInfo.trainVersionCode); + result = write(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount); if (result != trainVersionCodeByteCount) { VLOG("Failed to wrtie train version code"); close(fd); @@ -155,7 +183,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write # of bytes in trainName to file - const size_t trainNameSize = trainName.size(); + const size_t trainNameSize = trainInfo.trainName.size(); const size_t trainNameSizeByteCount = sizeof(trainNameSize); result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount); if (result != trainNameSizeByteCount) { @@ -165,7 +193,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write trainName to file - result = write(fd, trainName.c_str(), trainNameSize); + result = write(fd, trainInfo.trainName.c_str(), trainNameSize); if (result != trainNameSize) { VLOG("Failed to write train name"); close(fd); @@ -173,8 +201,8 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write status to file - const size_t statusByteCount = sizeof(status); - result = write(fd, (uint8_t*)&status, statusByteCount); + const size_t statusByteCount = sizeof(trainInfo.status); + result = write(fd, (uint8_t*)&trainInfo.status, statusByteCount); if (result != statusByteCount) { VLOG("Failed to write status"); close(fd); @@ -182,7 +210,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } // Write experiment id count to file. - const size_t experimentIdsCount = experimentIds.size(); + const size_t experimentIdsCount = trainInfo.experimentIds.size(); const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount); result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount); if (result != experimentIdsCountByteCount) { @@ -193,7 +221,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& // Write experimentIds to file for (size_t i = 0; i < experimentIdsCount; i++) { - const int64_t experimentId = experimentIds[i]; + const int64_t experimentId = trainInfo.experimentIds[i]; const size_t experimentIdByteCount = sizeof(experimentId); result = write(fd, &experimentId, experimentIdByteCount); if (result == experimentIdByteCount) { @@ -205,23 +233,47 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& } } - result = fchown(fd, AID_STATSD, AID_STATSD); - if (result) { - VLOG("Failed to chown train info file to statsd"); - close(fd); - return false; + // Write bools to file + const size_t boolByteCount = sizeof(trainInfo.requiresStaging); + result = write(fd, (uint8_t*)&trainInfo.requiresStaging, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write requires staging"); + close(fd); + return false; + } + + result = write(fd, (uint8_t*)&trainInfo.rollbackEnabled, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write rollback enabled"); + close(fd); + return false; + } + + result = write(fd, (uint8_t*)&trainInfo.requiresLowLatencyMonitor, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to write requires log latency monitor"); + close(fd); + return false; } close(fd); return true; } -bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { +bool StorageManager::readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo) { std::lock_guard<std::mutex> lock(sTrainInfoMutex); + return readTrainInfoLocked(trainName, trainInfo); +} - int fd = open(TRAIN_INFO_PATH, O_RDONLY | O_CLOEXEC); +bool StorageManager::readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo) { + trimToFit(TRAIN_INFO_DIR, /*parseTimestampOnly=*/ true); + const char* fileName = findTrainInfoFileNameLocked(trainName); + if (fileName == nullptr) { + return false; + } + int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName).c_str(), O_RDONLY | O_CLOEXEC); if (fd == -1) { - VLOG("Failed to open train-info.bin"); + VLOG("Failed to open %s", fileName); return false; } @@ -297,6 +349,29 @@ bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { trainInfo.experimentIds.push_back(experimentId); } + // Read bools + const size_t boolByteCount = sizeof(trainInfo.requiresStaging); + result = read(fd, &trainInfo.requiresStaging, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires requires staging from train info file"); + close(fd); + return false; + } + + result = read(fd, &trainInfo.rollbackEnabled, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires rollback enabled from train info file"); + close(fd); + return false; + } + + result = read(fd, &trainInfo.requiresLowLatencyMonitor, boolByteCount); + if (result != boolByteCount) { + VLOG("Failed to read requires requires low latency monitor from train info file"); + close(fd); + return false; + } + // Expect to be at EOF. char c; result = read(fd, &c, 1); @@ -311,6 +386,32 @@ bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { return true; } +vector<InstallTrainInfo> StorageManager::readAllTrainInfo() { + std::lock_guard<std::mutex> lock(sTrainInfoMutex); + vector<InstallTrainInfo> trainInfoList; + unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir); + if (dir == NULL) { + VLOG("Directory does not exist: %s", TRAIN_INFO_DIR); + return trainInfoList; + } + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') { + continue; + } + + InstallTrainInfo trainInfo; + bool readSuccess = StorageManager::readTrainInfoLocked(name, trainInfo); + if (!readSuccess) { + continue; + } + trainInfoList.push_back(trainInfo); + } + return trainInfoList; +} + void StorageManager::deleteFile(const char* file) { if (remove(file) != 0) { VLOG("Attempt to delete %s but is not found", file); @@ -574,7 +675,7 @@ void StorageManager::sortFiles(vector<FileInfo>* fileNames) { }); } -void StorageManager::trimToFit(const char* path) { +void StorageManager::trimToFit(const char* path, bool parseTimestampOnly) { unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir); if (dir == NULL) { VLOG("Path %s does not exist", path); @@ -589,7 +690,12 @@ void StorageManager::trimToFit(const char* path) { if (name[0] == '.') continue; FileName output; - parseFileName(name, &output); + if (parseTimestampOnly) { + output.mTimestampSec = StrToInt64(strtok(name, "_")); + output.mIsHistory = false; + } else { + parseFileName(name, &output); + } if (output.mTimestampSec == -1) continue; string file_name = output.getFullFileName(path); diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index 69b41c2cb974..d59046dfbb99 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -52,13 +52,22 @@ public: /** * Writes train info. */ - static bool writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, - int32_t status, const std::vector<int64_t>& experimentIds); + static bool writeTrainInfo(const InstallTrainInfo& trainInfo); /** * Reads train info. */ - static bool readTrainInfo(InstallTrainInfo& trainInfo); + static bool readTrainInfo(const std::string& trainName, InstallTrainInfo& trainInfo); + + /** + * Reads train info assuming lock is obtained. + */ + static bool readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo); + + /** + * Reads all train info and returns a vector of train info. + */ + static vector<InstallTrainInfo> readAllTrainInfo(); /** * Reads the file content to the buffer. @@ -124,7 +133,7 @@ public: * Trims files in the provided directory to limit the total size, number of * files, accumulation of outdated files. */ - static void trimToFit(const char* dir); + static void trimToFit(const char* dir, bool parseTimestampOnly = false); /** * Returns true if there already exists identical configuration on device. diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp index 160b57e27c6c..8fd6b46d0716 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp +++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp @@ -121,7 +121,7 @@ void SubscriberReporter::sendBroadcastLocked(const sp<IPendingIntentRef>& pir, subscription.id(), subscription.rule_id(), cookies, - getStatsDimensionsValue(dimKey.getDimensionKeyInWhat())); + dimKey.getDimensionKeyInWhat().toStatsDimensionsValueParcel()); } sp<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey, @@ -138,61 +138,6 @@ sp<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey return pirMapIt->second; } -void getStatsDimensionsValueHelper(const vector<FieldValue>& dims, size_t* index, int depth, - int prefix, vector<StatsDimensionsValue>* output) { - size_t count = dims.size(); - while (*index < count) { - const auto& dim = dims[*index]; - const int valueDepth = dim.mField.getDepth(); - const int valuePrefix = dim.mField.getPrefix(depth); - if (valueDepth > 2) { - ALOGE("Depth > 2 not supported"); - return; - } - if (depth == valueDepth && valuePrefix == prefix) { - switch (dim.mValue.getType()) { - case INT: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - dim.mValue.int_value)); - break; - case LONG: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - dim.mValue.long_value)); - break; - case FLOAT: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - dim.mValue.float_value)); - break; - case STRING: - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), - String16(dim.mValue.str_value.c_str()))); - break; - default: - break; - } - (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { - vector<StatsDimensionsValue> childOutput; - getStatsDimensionsValueHelper(dims, index, depth + 1, dim.mField.getPrefix(depth + 1), - &childOutput); - output->push_back(StatsDimensionsValue(dim.mField.getPosAtDepth(depth), childOutput)); - } else { - return; - } - } -} - -StatsDimensionsValue SubscriberReporter::getStatsDimensionsValue(const HashableDimensionKey& dim) { - if (dim.getValues().size() == 0) { - return StatsDimensionsValue(); - } - - vector<StatsDimensionsValue> fields; - size_t index = 0; - getStatsDimensionsValueHelper(dim.getValues(), &index, 0, 0, &fields); - return StatsDimensionsValue(dim.getValues()[0].mField.getTag(), fields); -} - } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h index 087a1b84b91f..42599f508313 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.h +++ b/cmds/statsd/src/subscriber/SubscriberReporter.h @@ -22,7 +22,6 @@ #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // subscription -#include "android/os/StatsDimensionsValue.h" #include "HashableDimensionKey.h" #include <mutex> @@ -70,8 +69,6 @@ public: sp<IPendingIntentRef> getBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId); - static StatsDimensionsValue getStatsDimensionsValue(const HashableDimensionKey& dim); - private: SubscriberReporter() {}; diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp index f4a59ed14d10..9e69d977f351 100644 --- a/cmds/statsd/tests/FieldValue_test.cpp +++ b/cmds/statsd/tests/FieldValue_test.cpp @@ -290,33 +290,34 @@ TEST(AtomMatcherTest, TestWriteDimensionPath) { } } -TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { - HashableDimensionKey dim; - - int pos1[] = {1, 1, 1}; - int pos2[] = {1, 1, 2}; - int pos3[] = {1, 1, 3}; - int pos4[] = {2, 0, 0}; - - Field field1(10, pos1, 2); - Field field2(10, pos2, 2); - Field field3(10, pos3, 2); - Field field4(10, pos4, 0); - - Value value1((int32_t)10025); - Value value2("tag"); - Value value3((int32_t)987654); - Value value4((int32_t)99999); - - dim.addValue(FieldValue(field1, value1)); - dim.addValue(FieldValue(field2, value2)); - dim.addValue(FieldValue(field3, value3)); - dim.addValue(FieldValue(field4, value4)); - - SubscriberReporter::getStatsDimensionsValue(dim); - // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't - // have any read api. -} +//TODO(b/149050405) Update this test for StatsDimensionValueParcel +//TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { +// HashableDimensionKey dim; +// +// int pos1[] = {1, 1, 1}; +// int pos2[] = {1, 1, 2}; +// int pos3[] = {1, 1, 3}; +// int pos4[] = {2, 0, 0}; +// +// Field field1(10, pos1, 2); +// Field field2(10, pos2, 2); +// Field field3(10, pos3, 2); +// Field field4(10, pos4, 0); +// +// Value value1((int32_t)10025); +// Value value2("tag"); +// Value value3((int32_t)987654); +// Value value4((int32_t)99999); +// +// dim.addValue(FieldValue(field1, value1)); +// dim.addValue(FieldValue(field2, value2)); +// dim.addValue(FieldValue(field3, value3)); +// dim.addValue(FieldValue(field4, value4)); +// +// SubscriberReporter::getStatsDimensionsValue(dim); +// // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't +// // have any read api. +//} TEST(AtomMatcherTest, TestWriteDimensionToProto) { HashableDimensionKey dim; diff --git a/cmds/statsd/tests/storage/StorageManager_test.cpp b/cmds/statsd/tests/storage/StorageManager_test.cpp index b91e5a0ad3a1..27a86e4230d8 100644 --- a/cmds/statsd/tests/storage/StorageManager_test.cpp +++ b/cmds/statsd/tests/storage/StorageManager_test.cpp @@ -40,40 +40,12 @@ TEST(StorageManagerTest, TrainInfoReadWriteTest) { bool result; - result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName, - trainInfo.status, trainInfo.experimentIds); + result = StorageManager::writeTrainInfo(trainInfo); EXPECT_TRUE(result); InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfoResult); - EXPECT_TRUE(result); - - EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); - EXPECT_EQ(trainInfo.trainName.size(), trainInfoResult.trainName.size()); - EXPECT_EQ(trainInfo.trainName, trainInfoResult.trainName); - EXPECT_EQ(trainInfo.status, trainInfoResult.status); - EXPECT_EQ(trainInfo.experimentIds.size(), trainInfoResult.experimentIds.size()); - EXPECT_EQ(trainInfo.experimentIds, trainInfoResult.experimentIds); -} - -TEST(StorageManagerTest, TrainInfoReadWriteEmptyTrainNameTest) { - InstallTrainInfo trainInfo; - trainInfo.trainVersionCode = 12345; - trainInfo.trainName = ""; - trainInfo.status = 1; - const char* expIds = "test_ids"; - trainInfo.experimentIds.assign(expIds, expIds + strlen(expIds)); - - bool result; - - result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName, - trainInfo.status, trainInfo.experimentIds); - - EXPECT_TRUE(result); - - InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfoResult); + result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult); EXPECT_TRUE(result); EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); @@ -94,13 +66,12 @@ TEST(StorageManagerTest, TrainInfoReadWriteTrainNameSizeOneTest) { bool result; - result = StorageManager::writeTrainInfo(trainInfo.trainVersionCode, trainInfo.trainName, - trainInfo.status, trainInfo.experimentIds); + result = StorageManager::writeTrainInfo(trainInfo); EXPECT_TRUE(result); InstallTrainInfo trainInfoResult; - result = StorageManager::readTrainInfo(trainInfoResult); + result = StorageManager::readTrainInfo(trainInfo.trainName, trainInfoResult); EXPECT_TRUE(result); EXPECT_EQ(trainInfo.trainVersionCode, trainInfoResult.trainVersionCode); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index db9aa18dbd5a..7ee44053d4d5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -94,6 +94,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Locale; @@ -3555,13 +3556,13 @@ public class ActivityManager { * @return a list of {@link ApplicationExitInfo} records matching the criteria, sorted in * the order from most recent to least recent. */ - @Nullable + @NonNull public List<ApplicationExitInfo> getHistoricalProcessExitReasons(@Nullable String packageName, @IntRange(from = 0) int pid, @IntRange(from = 0) int maxNum) { try { ParceledListSlice<ApplicationExitInfo> r = getService().getHistoricalProcessExitReasons( packageName, pid, maxNum, mContext.getUserId()); - return r == null ? null : r.getList(); + return r == null ? Collections.emptyList() : r.getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 6ef99a3def92..fc37af9d213e 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -3916,7 +3916,10 @@ public class AppOpsManager { } /** - * The features that have been used when checking the op + * The features that have been used when checking the op keyed by id of the feature. + * + * @see Context#createFeatureContext(String) + * @see #noteOp(String, int, String, String, String) */ @DataClass.Generated.Member public @NonNull Map<String,OpFeatureEntry> getFeatures() { diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index 4bf5f07e86be..c55453e94960 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -19,7 +19,6 @@ package android.app; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.app.ActivityManager.RunningAppProcessInfo.Importance; import android.icu.text.SimpleDateFormat; import android.os.Parcel; @@ -245,12 +244,12 @@ public final class ApplicationExitInfo implements Parcelable { /** * @see {@link #getPss} */ - private int mPss; + private long mPss; /** * @see {@link #getRss} */ - private int mRss; + private long mRss; /** * @see {@link #getTimestamp} @@ -385,7 +384,7 @@ public final class ApplicationExitInfo implements Parcelable { * it's NOT the exact memory information prior to its death; and it'll be zero * if the process died before system had a chance to take the sample. </p> */ - public int getPss() { + public long getPss() { return mPss; } @@ -396,12 +395,13 @@ public final class ApplicationExitInfo implements Parcelable { * it's NOT the exact memory information prior to its death; and it'll be zero * if the process died before system had a chance to take the sample. </p> */ - public int getRss() { + public long getRss() { return mRss; } /** - * The timestamp of the process's death, in milliseconds since the epoch. + * The timestamp of the process's death, in milliseconds since the epoch, + * as returned by {@link System#currentTimeMillis System.currentTimeMillis()}. */ public long getTimestamp() { return mTimestamp; @@ -409,6 +409,9 @@ public final class ApplicationExitInfo implements Parcelable { /** * The human readable description of the process's death, given by the system; could be null. + * + * <p class="note">Note: only intended to be human-readable and the system provides no + * guarantees that the format is stable across devices or Android releases.</p> */ public @Nullable String getDescription() { return mDescription; @@ -416,10 +419,7 @@ public final class ApplicationExitInfo implements Parcelable { /** * Return the user id of the record on a multi-user system. - * - * @hide */ - @SystemApi public @NonNull UserHandle getUserHandle() { return UserHandle.of(UserHandle.getUserId(mRealUid)); } @@ -546,7 +546,7 @@ public final class ApplicationExitInfo implements Parcelable { * * @hide */ - public void setPss(final int pss) { + public void setPss(final long pss) { mPss = pss; } @@ -555,7 +555,7 @@ public final class ApplicationExitInfo implements Parcelable { * * @hide */ - public void setRss(final int rss) { + public void setRss(final long rss) { mRss = rss; } @@ -630,8 +630,8 @@ public final class ApplicationExitInfo implements Parcelable { dest.writeInt(mSubReason); dest.writeInt(mStatus); dest.writeInt(mImportance); - dest.writeInt(mPss); - dest.writeInt(mRss); + dest.writeLong(mPss); + dest.writeLong(mRss); dest.writeLong(mTimestamp); dest.writeString(mDescription); } @@ -669,8 +669,8 @@ public final class ApplicationExitInfo implements Parcelable { mSubReason = in.readInt(); mStatus = in.readInt(); mImportance = in.readInt(); - mPss = in.readInt(); - mRss = in.readInt(); + mPss = in.readLong(); + mRss = in.readLong(); mTimestamp = in.readLong(); mDescription = in.readString(); } @@ -848,10 +848,10 @@ public final class ApplicationExitInfo implements Parcelable { mImportance = proto.readInt(ApplicationExitInfoProto.IMPORTANCE); break; case (int) ApplicationExitInfoProto.PSS: - mPss = proto.readInt(ApplicationExitInfoProto.PSS); + mPss = proto.readLong(ApplicationExitInfoProto.PSS); break; case (int) ApplicationExitInfoProto.RSS: - mRss = proto.readInt(ApplicationExitInfoProto.RSS); + mRss = proto.readLong(ApplicationExitInfoProto.RSS); break; case (int) ApplicationExitInfoProto.TIMESTAMP: mTimestamp = proto.readLong(ApplicationExitInfoProto.TIMESTAMP); @@ -891,8 +891,8 @@ public final class ApplicationExitInfo implements Parcelable { result = 31 * result + mSubReason; result = 31 * result + mImportance; result = 31 * result + mStatus; - result = 31 * result + mPss; - result = 31 * result + mRss; + result = 31 * result + (int) mPss; + result = 31 * result + (int) mRss; result = 31 * result + Long.hashCode(mTimestamp); result = 31 * result + Objects.hashCode(mProcessName); result = 31 * result + Objects.hashCode(mDescription); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 16c0910f1273..1d4a1acde434 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -88,6 +88,7 @@ interface INotificationManager void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList); void createNotificationChannels(String pkg, in ParceledListSlice channelsList); void createNotificationChannelsForPackage(String pkg, int uid, in ParceledListSlice channelsList); + ParceledListSlice getConversationsForPackage(String pkg, int uid); ParceledListSlice getNotificationChannelGroupsForPackage(String pkg, int uid, boolean includeDeleted); NotificationChannelGroup getNotificationChannelGroupForPackage(String groupId, String pkg, int uid); NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(String pkg, int uid, String groupId, boolean includeDeleted); @@ -96,7 +97,7 @@ interface INotificationManager NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId); NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId); void createConversationNotificationChannelForPackage(String pkg, int uid, String triggeringKey, in NotificationChannel parentChannel, String conversationId); - NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted); + NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, String conversationId, boolean includeDeleted); void deleteNotificationChannel(String pkg, String channelId); void deleteConversationNotificationChannels(String pkg, int uid, String conversationId); ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId); @@ -113,6 +114,7 @@ interface INotificationManager int getAppsBypassingDndCount(int uid); ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId); boolean isPackagePaused(String pkg); + void deleteNotificationHistoryItem(String pkg, int uid, long postedTime); void silenceNotificationSound(); diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl index 5d5956e4dca4..9d6c3d64aaf2 100644 --- a/core/java/android/app/ITaskOrganizerController.aidl +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -52,7 +52,11 @@ interface ITaskOrganizerController { boolean deleteRootTask(IWindowContainer task); /** Gets direct child tasks (ordered from top-to-bottom) */ - List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent); + List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent, + in int[] activityTypes); + + /** Gets all root tasks on a display (ordered from top-to-bottom) */ + List<ActivityManager.RunningTaskInfo> getRootTasks(int displayId, in int[] activityTypes); /** Get the root task which contains the current ime target */ IWindowContainer getImeTarget(int display); diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java index 909a476f3c94..f26e62874347 100644 --- a/core/java/android/app/NotificationHistory.java +++ b/core/java/android/app/NotificationHistory.java @@ -346,6 +346,26 @@ public final class NotificationHistory implements Parcelable { } /** + * Removes an individual historical notification and regenerates the string pool + */ + public boolean removeNotificationFromWrite(String packageName, long postedTime) { + boolean removed = false; + for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) { + HistoricalNotification hn = mNotificationsToWrite.get(i); + if (packageName.equals(hn.getPackage()) + && postedTime == hn.getPostedTimeMs()) { + removed = true; + mNotificationsToWrite.remove(i); + } + } + if (removed) { + poolStringsFromNotifications(); + } + + return removed; + } + + /** * Gets pooled strings in order to write them to disk */ public @NonNull String[] getPooledStringsToWrite() { diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 6f1effd228e3..5f74d2e1dd57 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -598,7 +598,8 @@ public class WallpaperManager { * is not able to access the wallpaper. */ public Drawable getDrawable() { - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, cmProxy); if (bm != null) { Drawable dr = new BitmapDrawable(mContext.getResources(), bm); dr.setDither(false); @@ -829,7 +830,8 @@ public class WallpaperManager { * null pointer if these is none. */ public Drawable peekDrawable() { - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, cmProxy); if (bm != null) { Drawable dr = new BitmapDrawable(mContext.getResources(), bm); dr.setDither(false); @@ -853,7 +855,8 @@ public class WallpaperManager { */ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public Drawable getFastDrawable() { - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, cmProxy); if (bm != null) { return new FastBitmapDrawable(bm); } @@ -869,7 +872,8 @@ public class WallpaperManager { */ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public Drawable peekFastDrawable() { - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, cmProxy); if (bm != null) { return new FastBitmapDrawable(bm); } @@ -892,10 +896,11 @@ public class WallpaperManager { if (!shouldEnableWideColorGamut()) { return false; } - Bitmap bitmap = sGlobals.peekWallpaperBitmap(mContext, false, which, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + Bitmap bitmap = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy); return bitmap != null && bitmap.getColorSpace() != null && bitmap.getColorSpace() != ColorSpace.get(ColorSpace.Named.SRGB) - && mCmProxy.isSupportedColorSpace(bitmap.getColorSpace()); + && cmProxy.isSupportedColorSpace(bitmap.getColorSpace()); } /** @@ -928,8 +933,8 @@ public class WallpaperManager { * @hide */ public Bitmap getBitmapAsUser(int userId, boolean hardware) { - return sGlobals.peekWallpaperBitmap( - mContext, true, FLAG_SYSTEM, userId, hardware, mCmProxy); + final ColorManagementProxy cmProxy = getColorManagementProxy(); + return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware, cmProxy); } /** @@ -2074,12 +2079,23 @@ public class WallpaperManager { } /** - * A private class to help Globals#getCurrentWallpaperLocked handle color management. + * Get the instance of {@link ColorManagementProxy}. + * + * @return instance of {@link ColorManagementProxy}. + * @hide */ - private static class ColorManagementProxy { + public ColorManagementProxy getColorManagementProxy() { + return mCmProxy; + } + + /** + * A hidden class to help {@link Globals#getCurrentWallpaperLocked} handle color management. + * @hide + */ + public static class ColorManagementProxy { private final Set<ColorSpace> mSupportedColorSpaces = new HashSet<>(); - ColorManagementProxy(Context context) { + public ColorManagementProxy(@NonNull Context context) { // Get a list of supported wide gamut color spaces. Display display = context.getDisplay(); if (display != null) { @@ -2087,9 +2103,14 @@ public class WallpaperManager { } } + @NonNull + public Set<ColorSpace> getSupportedColorSpaces() { + return mSupportedColorSpaces; + } + boolean isSupportedColorSpace(ColorSpace colorSpace) { return colorSpace != null && (colorSpace == ColorSpace.get(ColorSpace.Named.SRGB) - || mSupportedColorSpaces.contains(colorSpace)); + || getSupportedColorSpaces().contains(colorSpace)); } void doColorManagement(ImageDecoder decoder, ImageDecoder.ImageInfo info) { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 759979100483..dc15b51a442c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -8951,7 +8951,8 @@ public class DevicePolicyManager { * * <strong>Note: Starting from Android R, apps should no longer call this method with the * setting {@link android.provider.Settings.Secure#LOCATION_MODE}, which is deprecated. Instead, - * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}. + * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}. This will be + * enforced for all apps targeting Android R or above. * </strong> * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -8961,6 +8962,7 @@ public class DevicePolicyManager { */ public void setSecureSetting(@NonNull ComponentName admin, String setting, String value) { throwIfParentInstance("setSecureSetting"); + if (mService != null) { try { mService.setSecureSetting(admin, setting, value); @@ -9462,16 +9464,6 @@ public class DevicePolicyManager { * {@link android.os.Build.VERSION_CODES#M} the app-op matching the permission is set to * {@link android.app.AppOpsManager#MODE_IGNORED}, but the permission stays granted. * - * NOTE: Starting from Android R, location-related permissions cannot be granted by the - * admin: Calling this method with {@link #PERMISSION_GRANT_STATE_GRANTED} for any of the - * following permissions will return false: - * - * <ul> - * <li>{@code ACCESS_FINE_LOCATION}</li> - * <li>{@code ACCESS_BACKGROUND_LOCATION}</li> - * <li>{@code ACCESS_COARSE_LOCATION}</li> - * </ul> - * * @param admin Which profile or device owner this request is associated with. * @param packageName The application to grant or revoke a permission to. * @param permission The permission to grant or revoke. diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java index 5b367894ce69..e289a2775b79 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.compat.Compatibility; import android.content.Context; +import android.os.Binder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -70,11 +71,14 @@ public final class CompatChanges { @NonNull UserHandle user) { IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + final long token = Binder.clearCallingIdentity(); try { return platformCompat.isChangeEnabledByPackageName(changeId, packageName, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } finally { + Binder.restoreCallingIdentity(token); } } @@ -99,10 +103,13 @@ public final class CompatChanges { public static boolean isChangeEnabled(long changeId, int uid) { IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + final long token = Binder.clearCallingIdentity(); try { return platformCompat.isChangeEnabledByUid(changeId, uid); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } finally { + Binder.restoreCallingIdentity(token); } } } diff --git a/core/java/android/app/compat/TEST_MAPPING b/core/java/android/app/compat/TEST_MAPPING new file mode 100644 index 000000000000..c047df514e8d --- /dev/null +++ b/core/java/android/app/compat/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "frameworks/base/services/core/java/com/android/services/compat" + } + ] +}
\ No newline at end of file diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 01ccb86fb129..66bfcbd27ca6 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -26,6 +26,7 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.app.ActivityThread; +import android.app.PropertyInvalidatedCache; import android.bluetooth.BluetoothProfile.ConnectionPolicy; import android.bluetooth.le.BluetoothLeAdvertiser; import android.bluetooth.le.BluetoothLeScanner; @@ -994,6 +995,37 @@ public final class BluetoothAdapter { return false; } + private static final String BLUETOOTH_GET_STATE_CACHE_PROPERTY = "cache_key.bluetooth.get_state"; + + private final PropertyInvalidatedCache<Void, Integer> mBluetoothGetStateCache = + new PropertyInvalidatedCache<Void, Integer>( + 8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) { + @Override + protected Integer recompute(Void query) { + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.getState(); + } + } catch (RemoteException e) { + Log.e(TAG, "", e); + } finally { + mServiceLock.readLock().unlock(); + } + return BluetoothAdapter.STATE_OFF; + } + }; + + /** @hide */ + public void disableBluetoothGetStateCache() { + mBluetoothGetStateCache.disableLocal(); + } + + /** @hide */ + public static void invalidateBluetoothGetStateCache() { + PropertyInvalidatedCache.invalidateCache(BLUETOOTH_GET_STATE_CACHE_PROPERTY); + } + /** * Get the current state of the local Bluetooth adapter. * <p>Possible return values are @@ -1007,18 +1039,7 @@ public final class BluetoothAdapter { @RequiresPermission(Manifest.permission.BLUETOOTH) @AdapterState public int getState() { - int state = BluetoothAdapter.STATE_OFF; - - try { - mServiceLock.readLock().lock(); - if (mService != null) { - state = mService.getState(); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } + int state = mBluetoothGetStateCache.query(null); // Consider all internal states as OFF if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON @@ -1056,18 +1077,7 @@ public final class BluetoothAdapter { @UnsupportedAppUsage(publicAlternatives = "Use {@link #getState()} instead to determine " + "whether you can use BLE & BT classic.") public int getLeState() { - int state = BluetoothAdapter.STATE_OFF; - - try { - mServiceLock.readLock().lock(); - if (mService != null) { - state = mService.getState(); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } + int state = mBluetoothGetStateCache.query(null); if (VDBG) { Log.d(TAG, "getLeState() returning " + BluetoothAdapter.nameForState(state)); @@ -1960,6 +1970,38 @@ public final class BluetoothAdapter { } } + private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY = + "cache_key.bluetooth.is_offloaded_filtering_supported"; + private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache = + new PropertyInvalidatedCache<Void, Boolean>( + 8, BLUETOOTH_FILTERING_CACHE_PROPERTY) { + @Override + protected Boolean recompute(Void query) { + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.isOffloadedFilteringSupported(); + } + } catch (RemoteException e) { + Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e); + } finally { + mServiceLock.readLock().unlock(); + } + return false; + + } + }; + + /** @hide */ + public void disableIsOffloadedFilteringSupportedCache() { + mBluetoothFilteringCache.disableLocal(); + } + + /** @hide */ + public static void invalidateIsOffloadedFilteringSupportedCache() { + PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY); + } + /** * Return true if offloaded filters are supported * @@ -1969,17 +2011,7 @@ public final class BluetoothAdapter { if (!getLeAccess()) { return false; } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isOffloadedFilteringSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; + return mBluetoothFilteringCache.query(null); } /** @@ -2351,6 +2383,43 @@ public final class BluetoothAdapter { return BluetoothAdapter.STATE_DISCONNECTED; } + private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY = + "cache_key.bluetooth.get_profile_connection_state"; + private final PropertyInvalidatedCache<Integer, Integer> + mGetProfileConnectionStateCache = + new PropertyInvalidatedCache<Integer, Integer>( + 8, BLUETOOTH_PROFILE_CACHE_PROPERTY) { + @Override + protected Integer recompute(Integer query) { + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.getProfileConnectionState(query); + } + } catch (RemoteException e) { + Log.e(TAG, "getProfileConnectionState:", e); + } finally { + mServiceLock.readLock().unlock(); + } + return BluetoothProfile.STATE_DISCONNECTED; + } + @Override + public String queryToString(Integer query) { + return String.format("getProfileConnectionState(profile=\"%d\")", + query); + } + }; + + /** @hide */ + public void disableGetProfileConnectionStateCache() { + mGetProfileConnectionStateCache.disableLocal(); + } + + /** @hide */ + public static void invalidateGetProfileConnectionStateCache() { + PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY); + } + /** * Get the current connection state of a profile. * This function can be used to check whether the local Bluetooth adapter @@ -2368,17 +2437,7 @@ public final class BluetoothAdapter { if (getState() != STATE_ON) { return BluetoothProfile.STATE_DISCONNECTED; } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getProfileConnectionState(profile); - } - } catch (RemoteException e) { - Log.e(TAG, "getProfileConnectionState:", e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothProfile.STATE_DISCONNECTED; + return mGetProfileConnectionStateCache.query(new Integer(profile)); } /** diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index f32a4ab43357..0e0161ff4e9f 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -700,6 +700,27 @@ public abstract class ContentResolver implements ContentInterface { /** @hide */ public static final String REMOTE_CALLBACK_RESULT = "result"; + /** + * How long we wait for an attached process to publish its content providers + * before we decide it must be hung. + * @hide + */ + public static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS = 10 * 1000; + + /** + * How long we wait for an provider to be published. Should be longer than + * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS}. + * @hide + */ + public static final int CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS = + CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS + 10 * 1000; + + // Should be >= {@link #CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS}, because that's how + // long ActivityManagerService is giving a content provider to get published if a new process + // needs to be started for that. + private static final int GET_TYPE_TIMEOUT_MILLIS = + CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS + 5 * 1000; + public ContentResolver(@Nullable Context context) { this(context, null); } @@ -849,8 +870,6 @@ public abstract class ContentResolver implements ContentInterface { } } - private static final int GET_TYPE_TIMEOUT_MILLIS = 3000; - private static class GetTypeResultListener implements RemoteCallback.OnResultListener { @GuardedBy("this") public boolean done; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6f8a99fce897..c37c1c1a2f31 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4289,7 +4289,9 @@ public class Intent implements Parcelable, Cloneable { * intent filter in their manifests, so that they can be looked up and bound to by * {@code DataLoaderManagerService}. * - * Data loader service providers must be privileged apps. + * <p class="note">This is a protected intent that can only be sent by the system. + * + * Data loader service providers must be privileged apps. * See {@link com.android.server.pm.PackageManagerShellCommandDataLoader} as an example of such * data loader service provider. * diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java index 351edc92888d..cd5117be6123 100644 --- a/core/java/android/content/integrity/AppInstallMetadata.java +++ b/core/java/android/content/integrity/AppInstallMetadata.java @@ -18,6 +18,7 @@ package android.content.integrity; import android.annotation.NonNull; +import java.util.List; import java.util.Objects; /** @@ -33,18 +34,18 @@ import java.util.Objects; public final class AppInstallMetadata { private final String mPackageName; // Raw string encoding for the SHA-256 hash of the certificate of the app. - private final String mAppCertificate; + private final List<String> mAppCertificates; private final String mInstallerName; // Raw string encoding for the SHA-256 hash of the certificate of the installer. - private final String mInstallerCertificate; + private final List<String> mInstallerCertificates; private final long mVersionCode; private final boolean mIsPreInstalled; private AppInstallMetadata(Builder builder) { this.mPackageName = builder.mPackageName; - this.mAppCertificate = builder.mAppCertificate; + this.mAppCertificates = builder.mAppCertificates; this.mInstallerName = builder.mInstallerName; - this.mInstallerCertificate = builder.mInstallerCertificate; + this.mInstallerCertificates = builder.mInstallerCertificates; this.mVersionCode = builder.mVersionCode; this.mIsPreInstalled = builder.mIsPreInstalled; } @@ -55,8 +56,8 @@ public final class AppInstallMetadata { } @NonNull - public String getAppCertificate() { - return mAppCertificate; + public List<String> getAppCertificates() { + return mAppCertificates; } @NonNull @@ -65,8 +66,8 @@ public final class AppInstallMetadata { } @NonNull - public String getInstallerCertificate() { - return mInstallerCertificate; + public List<String> getInstallerCertificates() { + return mInstallerCertificates; } /** @see AppInstallMetadata.Builder#setVersionCode(long) */ @@ -82,12 +83,12 @@ public final class AppInstallMetadata { @Override public String toString() { return String.format( - "AppInstallMetadata { PackageName = %s, AppCert = %s, InstallerName = %s," - + " InstallerCert = %s, VersionCode = %d, PreInstalled = %b }", + "AppInstallMetadata { PackageName = %s, AppCerts = %s, InstallerName = %s," + + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b }", mPackageName, - mAppCertificate, + mAppCertificates, mInstallerName == null ? "null" : mInstallerName, - mInstallerCertificate == null ? "null" : mInstallerCertificate, + mInstallerCertificates == null ? "null" : mInstallerCertificates, mVersionCode, mIsPreInstalled); } @@ -95,9 +96,9 @@ public final class AppInstallMetadata { /** Builder class for constructing {@link AppInstallMetadata} objects. */ public static final class Builder { private String mPackageName; - private String mAppCertificate; + private List<String> mAppCertificates; private String mInstallerName; - private String mInstallerCertificate; + private List<String> mInstallerCertificates; private long mVersionCode; private boolean mIsPreInstalled; @@ -118,11 +119,11 @@ public final class AppInstallMetadata { * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate * of the app. * - * @see AppInstallMetadata#getAppCertificate() + * @see AppInstallMetadata#getAppCertificates() */ @NonNull - public Builder setAppCertificate(@NonNull String appCertificate) { - this.mAppCertificate = Objects.requireNonNull(appCertificate); + public Builder setAppCertificates(@NonNull List<String> appCertificates) { + this.mAppCertificates = Objects.requireNonNull(appCertificates); return this; } @@ -143,11 +144,11 @@ public final class AppInstallMetadata { * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate * of the installer. * - * @see AppInstallMetadata#getInstallerCertificate() + * @see AppInstallMetadata#getInstallerCertificates() */ @NonNull - public Builder setInstallerCertificate(@NonNull String installerCertificate) { - this.mInstallerCertificate = Objects.requireNonNull(installerCertificate); + public Builder setInstallerCertificates(@NonNull List<String> installerCertificates) { + this.mInstallerCertificates = Objects.requireNonNull(installerCertificates); return this; } @@ -181,7 +182,7 @@ public final class AppInstallMetadata { @NonNull public AppInstallMetadata build() { Objects.requireNonNull(mPackageName); - Objects.requireNonNull(mAppCertificate); + Objects.requireNonNull(mAppCertificates); return new AppInstallMetadata(this); } } diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java index d25f413bb65f..42459779e212 100644 --- a/core/java/android/content/integrity/AtomicFormula.java +++ b/core/java/android/content/integrity/AtomicFormula.java @@ -30,6 +30,8 @@ import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -387,7 +389,7 @@ public abstract class AtomicFormula extends IntegrityFormula { if (mValue == null || mIsHashedValue == null) { return false; } - return getStringMetadataValue(appInstallMetadata, getKey()).equals(mValue); + return getMetadataValue(appInstallMetadata, getKey()).contains(mValue); } @Override @@ -448,17 +450,17 @@ public abstract class AtomicFormula extends IntegrityFormula { return mIsHashedValue; } - private static String getStringMetadataValue( + private static List<String> getMetadataValue( AppInstallMetadata appInstallMetadata, int key) { switch (key) { case AtomicFormula.PACKAGE_NAME: - return appInstallMetadata.getPackageName(); + return Collections.singletonList(appInstallMetadata.getPackageName()); case AtomicFormula.APP_CERTIFICATE: - return appInstallMetadata.getAppCertificate(); + return appInstallMetadata.getAppCertificates(); case AtomicFormula.INSTALLER_CERTIFICATE: - return appInstallMetadata.getInstallerCertificate(); + return appInstallMetadata.getInstallerCertificates(); case AtomicFormula.INSTALLER_NAME: - return appInstallMetadata.getInstallerName(); + return Collections.singletonList(appInstallMetadata.getInstallerName()); default: throw new IllegalStateException( "Unexpected key in StringAtomicFormula: " + key); diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 04923590b413..38a9ac4a0d05 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -98,4 +98,9 @@ interface ILauncherApps { in ComponentName componentName, int flags, in IShortcutChangeCallback callback, int callbackId); void unregisterShortcutChangeCallback(String callingPackage, int callbackId); + + void cacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, + in UserHandle user); + void uncacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds, + in UserHandle user); } diff --git a/core/java/android/content/pm/InstallationFile.java b/core/java/android/content/pm/InstallationFile.java index 111ad32d1e41..b449945628d2 100644 --- a/core/java/android/content/pm/InstallationFile.java +++ b/core/java/android/content/pm/InstallationFile.java @@ -16,82 +16,59 @@ package android.content.pm; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Defines the properties of a file in an installation session. - * TODO(b/136132412): update with new APIs. - * * @hide */ @SystemApi public final class InstallationFile implements Parcelable { - public static final int FILE_TYPE_UNKNOWN = -1; - public static final int FILE_TYPE_APK = 0; - public static final int FILE_TYPE_LIB = 1; - public static final int FILE_TYPE_OBB = 2; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"FILE_TYPE_"}, value = { - FILE_TYPE_APK, - FILE_TYPE_LIB, - FILE_TYPE_OBB, - }) - public @interface FileType { - } - - private String mFileName; - private @FileType int mFileType; - private long mFileSize; - private byte[] mMetadata; - - public InstallationFile(@NonNull String fileName, long fileSize, - @Nullable byte[] metadata) { - mFileName = fileName; - mFileSize = fileSize; + private final @PackageInstaller.FileLocation int mLocation; + private final @NonNull String mName; + private final long mLengthBytes; + private final @Nullable byte[] mMetadata; + private final @Nullable byte[] mSignature; + + public InstallationFile(@PackageInstaller.FileLocation int location, @NonNull String name, + long lengthBytes, @Nullable byte[] metadata, @Nullable byte[] signature) { + mLocation = location; + mName = name; + mLengthBytes = lengthBytes; mMetadata = metadata; - if (fileName.toLowerCase().endsWith(".apk")) { - mFileType = FILE_TYPE_APK; - } else if (fileName.toLowerCase().endsWith(".obb")) { - mFileType = FILE_TYPE_OBB; - } else if (fileName.toLowerCase().endsWith(".so") && fileName.toLowerCase().startsWith( - "lib/")) { - mFileType = FILE_TYPE_LIB; - } else { - mFileType = FILE_TYPE_UNKNOWN; - } + mSignature = signature; } - public @FileType int getFileType() { - return mFileType; + public @PackageInstaller.FileLocation int getLocation() { + return mLocation; } public @NonNull String getName() { - return mFileName; + return mName; } - public long getSize() { - return mFileSize; + public long getLengthBytes() { + return mLengthBytes; } public @Nullable byte[] getMetadata() { return mMetadata; } + public @Nullable byte[] getSignature() { + return mSignature; + } + private InstallationFile(Parcel source) { - mFileName = source.readString(); - mFileType = source.readInt(); - mFileSize = source.readLong(); + mLocation = source.readInt(); + mName = source.readString(); + mLengthBytes = source.readLong(); mMetadata = source.createByteArray(); + mSignature = source.createByteArray(); } @Override @@ -101,10 +78,11 @@ public final class InstallationFile implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(mFileName); - dest.writeInt(mFileType); - dest.writeLong(mFileSize); + dest.writeInt(mLocation); + dest.writeString(mName); + dest.writeLong(mLengthBytes); dest.writeByteArray(mMetadata); + dest.writeByteArray(mSignature); } public static final @NonNull Creator<InstallationFile> CREATOR = diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 73c9e4d843b7..70603b472551 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -16,10 +16,12 @@ package android.content.pm; +import static android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; @@ -600,11 +602,15 @@ public class LauncherApps { } /** - * Show an error log on logcat, when the calling user is a managed profile, and the target - * user is different from the calling user, in order to help developers to detect it. + * Show an error log on logcat, when the calling user is a managed profile, the target + * user is different from the calling user, and it is not called from a package that has the + * {@link permission.INTERACT_ACROSS_USERS_FULL} permission, in order to help + * developers to detect it. */ private void logErrorForInvalidProfileAccess(@NonNull UserHandle target) { - if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()) { + if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile() + && mContext.checkSelfPermission(permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "Accessing other profiles/users from managed profile is no longer allowed."); } } @@ -1089,6 +1095,61 @@ public class LauncherApps { } /** + * Mark shortcuts as cached for a package. + * + * <p>Only dynamic long lived shortcuts can be cached. None dynamic or non long lived shortcuts + * in the list will be ignored. + * + * <p>Unlike pinned shortcuts, where different callers can have different sets of pinned + * shortcuts, cached state is per shortcut only, and even if multiple callers cache the same + * shortcut, it can be uncached by any valid caller. + * + * @param packageName The target package name. + * @param shortcutIds The IDs of the shortcut to be cached. + * @param user The UserHandle of the profile. + * @throws IllegalStateException when the user is locked, or when the {@code user} user + * is locked or not running. + * + * @see ShortcutManager + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) + public void cacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds, + @NonNull UserHandle user) { + logErrorForInvalidProfileAccess(user); + try { + mService.cacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove cached flag from shortcuts for a package. + * + * @param packageName The target package name. + * @param shortcutIds The IDs of the shortcut to be uncached. + * @param user The UserHandle of the profile. + * @throws IllegalStateException when the user is locked, or when the {@code user} user + * is locked or not running. + * + * @see ShortcutManager + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) + public void uncacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds, + @NonNull UserHandle user) { + logErrorForInvalidProfileAccess(user); + try { + mService.uncacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @hide kept for testing. */ @Deprecated diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9f7f4821a11e..e8668f1368fb 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -597,6 +597,7 @@ public abstract class PackageManager { * @hide */ @SystemApi + @TestApi public static final int MODULE_APEX_NAME = 0x00000001; /** @hide */ diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java index a11a1dd5a68b..a69905eb3de4 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -85,4 +85,11 @@ public abstract class ShortcutServiceInternal { public abstract boolean isForegroundDefaultLauncher(@NonNull String callingPackage, int callingUid); + + public abstract void cacheShortcuts(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId); + public abstract void uncacheShortcuts(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId); } diff --git a/core/java/android/content/pm/parsing/AndroidPackage.java b/core/java/android/content/pm/parsing/AndroidPackage.java index fbe5a48ad61e..da17ff3cdefc 100644 --- a/core/java/android/content/pm/parsing/AndroidPackage.java +++ b/core/java/android/content/pm/parsing/AndroidPackage.java @@ -286,6 +286,8 @@ public interface AndroidPackage extends Parcelable { List<String> getQueriesPackages(); + Set<String> getQueriesProviders(); + String getRealPackage(); // TODO(b/135203078): Rename to getRequiredFeatures? Somewhat ambiguous whether "Req" is diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java index 5c8c9a41a520..548d82a6ab76 100644 --- a/core/java/android/content/pm/parsing/ApkParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkParseUtils.java @@ -96,6 +96,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringTokenizer; /** @hide */ public class ApkParseUtils { @@ -1817,6 +1818,25 @@ public class ApkParseUtils { ); } parsingPackage.addQueriesPackage(packageName.intern()); + } else if (parser.getName().equals("provider")) { + final TypedArray sa = res.obtainAttributes(parser, + R.styleable.AndroidManifestQueriesProvider); + try { + final String authorities = + sa.getString(R.styleable.AndroidManifestQueriesProvider_authorities); + if (TextUtils.isEmpty(authorities)) { + return parseInput.error( + PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, + "Authority missing from provider tag." + ); + } + StringTokenizer authoritiesTokenizer = new StringTokenizer(authorities, ";"); + while (authoritiesTokenizer.hasMoreElements()) { + parsingPackage.addQueriesProvider(authoritiesTokenizer.nextToken()); + } + } finally { + sa.recycle(); + } } } return parseInput.success(parsingPackage); diff --git a/core/java/android/content/pm/parsing/PackageImpl.java b/core/java/android/content/pm/parsing/PackageImpl.java index fe8307c7c8cd..0df950006f43 100644 --- a/core/java/android/content/pm/parsing/PackageImpl.java +++ b/core/java/android/content/pm/parsing/PackageImpl.java @@ -216,6 +216,9 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android private ArrayList<String> queriesPackages; @Nullable + private ArraySet<String> queriesProviders; + + @Nullable private ArrayMap<String, ComponentParseUtils.ParsedProcess> processes; private String[] splitClassLoaderNames; @@ -957,6 +960,12 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android } @Override + public ParsingPackage addQueriesProvider(String authority) { + this.queriesProviders = ArrayUtils.add(this.queriesProviders, authority); + return this; + } + + @Override public PackageImpl setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes) { this.processes = processes; return this; @@ -2975,6 +2984,11 @@ public final class PackageImpl implements ParsingPackage, ParsedPackage, Android return queriesPackages; } + @Override + public Set<String> getQueriesProviders() { + return queriesProviders; + } + private static void internStringArrayList(List<String> list) { if (list != null) { final int N = list.size(); diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index 9ddcc0995fd4..a2fe064b66c3 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -100,6 +100,8 @@ public interface ParsingPackage extends AndroidPackage { ParsingPackage addQueriesPackage(String packageName); + ParsingPackage addQueriesProvider(String authority); + ParsingPackage setProcesses(ArrayMap<String, ComponentParseUtils.ParsedProcess> processes); ParsingPackage asSplit( diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 8c358cc522d6..6a9e0aa047d1 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -45,6 +45,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.UiModeManager; import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; import android.content.LocaleProto; @@ -1975,6 +1976,15 @@ public final class Configuration implements Parcelable, Comparable<Configuration readFromParcel(source); } + + /** + * Retuns whether the configuration is in night mode + * @return true if night mode is active and false otherwise + */ + public boolean isNightModeActive() { + return (uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES; + } + public int compareTo(Configuration that) { int n; float a = this.fontScale; diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java index 9bd39925f83f..c1df5b6d2e7e 100644 --- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java +++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java @@ -221,10 +221,8 @@ public class SoundTriggerModule { /** * Set a model specific {@link ModelParams} with the given value. This * parameter will keep its value for the duration the model is loaded regardless of starting - * and - * stopping recognition. Once the model is unloaded, the value will be lost. - * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before calling - * this method. + * and stopping recognition. Once the model is unloaded, the value will be lost. + * {@link #queryParameter} should be checked first before calling this method. * * @param soundModelHandle handle of model to apply parameter * @param modelParam {@link ModelParams} @@ -251,22 +249,14 @@ public class SoundTriggerModule { * for the duration the model is loaded regardless of starting and stopping recognition. * Once the model is unloaded, the value will be lost. If the value is not set, a default * value is returned. See {@link ModelParams} for parameter default values. - * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before + * {@link #queryParameter} should be checked first before * calling this method. Otherwise, an exception can be thrown. * * @param soundModelHandle handle of model to get parameter * @param modelParam {@link ModelParams} * @return value of parameter - * @throws UnsupportedOperationException if hal or model do not support this API. - * {@link SoundTriggerModule#queryParameter(int, int)} - * should - * be checked first. - * @throws IllegalArgumentException if invalid model handle or parameter is passed. - * {@link SoundTriggerModule#queryParameter(int, int)} - * should be checked first. */ - public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam) - throws UnsupportedOperationException, IllegalArgumentException { + public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam) { try { return mService.getModelParameter(soundModelHandle, ConversionUtil.api2aidlModelParameter(modelParam)); @@ -276,9 +266,8 @@ public class SoundTriggerModule { } /** - * Determine if parameter control is supported for the given model handle. - * This method should be checked prior to calling {@link SoundTriggerModule#setParameter} or - * {@link SoundTriggerModule#getParameter}. + * Query the parameter support and range for a given {@link ModelParams}. + * This method should be check prior to calling {@link #setParameter} or {@link #getParameter}. * * @param soundModelHandle handle of model to get parameter * @param modelParam {@link ModelParams} diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java index 26d9c7d687eb..75123528a6b8 100644 --- a/core/java/android/os/SharedMemory.java +++ b/core/java/android/os/SharedMemory.java @@ -27,6 +27,7 @@ import dalvik.system.VMRuntime; import java.io.Closeable; import java.io.FileDescriptor; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.DirectByteBuffer; import java.nio.NioUtils; @@ -272,6 +273,20 @@ public final class SharedMemory implements Parcelable, Closeable { dest.writeFileDescriptor(mFileDescriptor); } + /** + * Returns a dup'd ParcelFileDescriptor from the SharedMemory FileDescriptor. + * This obeys standard POSIX semantics, where the + * new file descriptor shared state such as file position with the + * original file descriptor. + * TODO: propose this method as a public or system API for next release to achieve parity with + * NDK ASharedMemory_dupFromJava. + * + * @hide + */ + public ParcelFileDescriptor getFdDup() throws IOException { + return ParcelFileDescriptor.dup(mFileDescriptor); + } + public static final @android.annotation.NonNull Parcelable.Creator<SharedMemory> CREATOR = new Parcelable.Creator<SharedMemory>() { @Override diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index 21434a2aecba..9d98b3b7819b 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -103,4 +103,9 @@ interface IIncrementalService { * Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader. */ void deleteStorage(int storageId); + + /** + * Setting up native library directories and extract native libs onto a storage. + */ + boolean configureNativeBinaries(int storageId, in @utf8InCpp String apkFullPath, in @utf8InCpp String libDirRelativePath, in @utf8InCpp String abi); } diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java index 987a53e337a0..25fb3e0ed907 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -29,6 +29,8 @@ package android.os.incremental; * @throws IllegalStateException the session is not an Incremental installation session. */ +import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -85,7 +87,7 @@ public final class IncrementalFileStorages { try { result = new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams); for (InstallationFile file : addedFiles) { - if (file.getFileType() == InstallationFile.FILE_TYPE_APK) { + if (file.getLocation() == LOCATION_DATA_APP) { try { result.addApkFile(file); } catch (IOException e) { @@ -95,7 +97,7 @@ public final class IncrementalFileStorages { e); } } else { - throw new IOException("Unknown file type: " + file.getFileType()); + throw new IOException("Unknown file location: " + file.getLocation()); } } @@ -147,8 +149,8 @@ public final class IncrementalFileStorages { String apkName = apk.getName(); File targetFile = Paths.get(stageDirPath, apkName).toFile(); if (!targetFile.exists()) { - mDefaultStorage.makeFile(apkName, apk.getSize(), null, - apk.getMetadata(), 0, null, null, null); + mDefaultStorage.makeFile(apkName, apk.getLengthBytes(), null, apk.getMetadata(), + apk.getSignature()); } } diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 0024ac7a6b2e..d2d8f85b1b35 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -229,16 +229,41 @@ public final class IncrementalManager { if (linkedApkStorage == null) { throw new IOException("Failed to create linked storage at dir: " + afterCodePathParent); } - linkedApkStorage.makeDirectory(afterCodePathName); - File[] files = beforeCodeFile.listFiles(); - for (int i = 0; i < files.length; i++) { - if (files[i].isFile()) { - String fileName = files[i].getName(); - apkStorage.makeLink( - fileName, linkedApkStorage, afterCodePathName + "/" + fileName); + linkFiles(apkStorage, beforeCodeFile, "", linkedApkStorage, afterCodePathName); + apkStorage.unBind(beforeCodePath); + } + + /** + * Recursively set up directories and link all the files from source storage to target storage. + * + * @param sourceStorage The storage that has all the files and directories underneath. + * @param sourceAbsolutePath The absolute path of the directory that holds all files and dirs. + * @param sourceRelativePath The relative path on the source directory, e.g., "" or "lib". + * @param targetStorage The target storage that will have the same files and directories. + * @param targetRelativePath The relative path to the directory on the target storage that + * should have all the files and dirs underneath, + * e.g., "packageName-random". + * @throws IOException When makeDirectory or makeLink fails on the Incremental File System. + */ + private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath, + String sourceRelativePath, IncrementalStorage targetStorage, + String targetRelativePath) throws IOException { + targetStorage.makeDirectory(targetRelativePath); + final File[] entryList = sourceAbsolutePath.listFiles(); + for (int i = 0; i < entryList.length; i++) { + final File entry = entryList[i]; + final String entryName = entryList[i].getName(); + final String sourceEntryRelativePath = + sourceRelativePath.isEmpty() ? entryName : sourceRelativePath + "/" + entryName; + final String targetEntryRelativePath = targetRelativePath + "/" + entryName; + if (entry.isFile()) { + sourceStorage.makeLink( + sourceEntryRelativePath, targetStorage, targetEntryRelativePath); + } else if (entry.isDirectory()) { + linkFiles(sourceStorage, entry, sourceEntryRelativePath, targetStorage, + targetEntryRelativePath); } } - apkStorage.unBind(beforeCodePath); } /** @@ -274,7 +299,16 @@ public final class IncrementalManager { return nativeIsIncrementalPath(path); } + /** + * Returns raw signature for file if it's on Incremental File System. + * Unsafe, use only if you are sure what you are doing. + */ + public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) { + return nativeUnsafeGetFileSignature(path); + } + /* Native methods */ private static native boolean nativeIsEnabled(); private static native boolean nativeIsIncrementalPath(@NonNull String path); + private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path); } diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index c4b843b6ce33..f4e1f967dca8 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; @@ -169,10 +171,11 @@ public final class IncrementalStorage { * @param path Relative path of the new file. * @param size Size of the new file in bytes. * @param metadata Metadata bytes. + * @param v4signatureBytes Serialized V4SignatureProto. */ 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 { + @Nullable byte[] metadata, @Nullable byte[] v4signatureBytes) + throws IOException { try { if (id == null && metadata == null) { throw new IOException("File ID and metadata cannot both be null"); @@ -181,13 +184,7 @@ public final class IncrementalStorage { params.size = size; params.metadata = (metadata == null ? new byte[0] : 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; - } + params.signature = parseV4Signature(v4signatureBytes); int res = mService.makeFile(mId, path, params); if (res != 0) { throw new IOException("makeFile() failed with errno " + -res); @@ -197,6 +194,7 @@ 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. @@ -349,6 +347,37 @@ public final class IncrementalStorage { } } + /** + * 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 UUID id) { + try { + final byte[] rawId = idToBytes(id); + return mService.getMetadataById(mId, rawId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** + * Informs the data loader service associated with the current storage to start data loader + * + * @return True if data loader is successfully started. + */ + public boolean startLoading() { + try { + return mService.startLoading(mId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return false; + } + } + private static final int UUID_BYTE_SIZE = 16; /** @@ -386,31 +415,60 @@ public final class IncrementalStorage { return new UUID(msb, lsb); } + private static final int INCFS_HASH_SHA256 = 1; + private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256 + private static final int INCFS_MAX_ADD_DATA_SIZE = 128; + /** - * Returns the metadata object of an IncFs File. - * - * @param id The file id. - * @return Byte array that contains metadata bytes. + * Deserialize and validate v4 signature bytes. */ - @Nullable - public byte[] getFileMetadata(@NonNull UUID id) { - try { - final byte[] rawId = idToBytes(id); - return mService.getMetadataById(mId, rawId); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes) + throws IOException { + if (v4signatureBytes == null) { return null; } + + final V4Signature signature; + try (DataInputStream input = new DataInputStream( + new ByteArrayInputStream(v4signatureBytes))) { + signature = V4Signature.readFrom(input); + } + + final byte[] rootHash = signature.verityRootHash; + final byte[] additionalData = signature.v3Digest; + final byte[] pkcs7Signature = signature.pkcs7SignatureBlock; + + if (rootHash.length != INCFS_MAX_HASH_SIZE) { + throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes"); + } + if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) { + throw new IOException( + "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes"); + } + + IncrementalSignature result = new IncrementalSignature(); + result.hashAlgorithm = INCFS_HASH_SHA256; + result.rootHash = rootHash; + result.additionalData = additionalData; + result.signature = pkcs7Signature; + + return result; } /** - * Informs the data loader service associated with the current storage to start data loader + * Configure all the lib files inside Incremental Service, e.g., create lib dirs, create new lib + * files, extract original lib file data from zip and then write data to the lib files on the + * Incremental File System. * - * @return True if data loader is successfully started. + * @param apkFullPath Source APK to extract native libs from. + * @param libDirRelativePath Target dir to put lib files, e.g., "lib" or "lib/arm". + * @param abi Target ABI of the native lib files. Only extract native libs of this ABI. + * @return Success of not. */ - public boolean startLoading() { + public boolean configureNativeBinaries(String apkFullPath, String libDirRelativePath, + String abi) { try { - return mService.startLoading(mId); + return mService.configureNativeBinaries(mId, apkFullPath, libDirRelativePath, abi); } catch (RemoteException e) { e.rethrowFromSystemServer(); return false; diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java new file mode 100644 index 000000000000..5fadee4355b3 --- /dev/null +++ b/core/java/android/os/incremental/V4Signature.java @@ -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 android.os.incremental; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * V4 signature fields. + * Keep in sync with APKSig master copy. + * @hide + */ +public class V4Signature { + public final byte[] verityRootHash; + public final byte[] v3Digest; + public final byte[] pkcs7SignatureBlock; + + V4Signature(byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) { + this.verityRootHash = verityRootHash; + this.v3Digest = v3Digest; + this.pkcs7SignatureBlock = pkcs7SignatureBlock; + } + + static byte[] readBytes(DataInputStream stream) throws IOException { + byte[] result = new byte[stream.readInt()]; + stream.read(result); + return result; + } + + static V4Signature readFrom(DataInputStream stream) throws IOException { + byte[] verityRootHash = readBytes(stream); + byte[] v3Digest = readBytes(stream); + byte[] pkcs7SignatureBlock = readBytes(stream); + return new V4Signature(verityRootHash, v3Digest, pkcs7SignatureBlock); + } + + static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException { + stream.writeInt(bytes.length); + stream.write(bytes); + } + + void writeTo(DataOutputStream stream) throws IOException { + writeBytes(stream, this.verityRootHash); + writeBytes(stream, this.v3Digest); + writeBytes(stream, this.pkcs7SignatureBlock); + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 30e4eee08e8d..b9207e5a12d5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1830,6 +1830,17 @@ public final class Settings { public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID"; /** + * Activity Extra: The {@link NotificationChannel#getConversationId()} of the notification + * conversation settings to display. + * <p> + * This is an optional extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}. If + * included the system will first look up notification settings by channel and conversation id, + * and will fall back to channel id if a specialized channel for this conversation doesn't + * exist, similar to {@link NotificationManager#getNotificationChannel(String, String)}. + */ + public static final String EXTRA_CONVERSATION_ID = "android.provider.extra.CONVERSATION_ID"; + + /** * Activity Action: Show notification redaction settings. * * @hide diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java index c21577842199..b9700b2b80db 100644 --- a/core/java/android/service/dataloader/DataLoaderService.java +++ b/core/java/android/service/dataloader/DataLoaderService.java @@ -90,7 +90,7 @@ public abstract class DataLoaderService extends Service { * @hide */ @SystemApi - public @Nullable DataLoader onCreateDataLoader() { + public @Nullable DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) { return null; } diff --git a/core/java/android/service/notification/ConversationChannelWrapper.aidl b/core/java/android/service/notification/ConversationChannelWrapper.aidl new file mode 100644 index 000000000000..f3241586e252 --- /dev/null +++ b/core/java/android/service/notification/ConversationChannelWrapper.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +parcelable ConversationChannelWrapper;
\ No newline at end of file diff --git a/core/java/android/service/notification/ConversationChannelWrapper.java b/core/java/android/service/notification/ConversationChannelWrapper.java new file mode 100644 index 000000000000..9847695ca5ee --- /dev/null +++ b/core/java/android/service/notification/ConversationChannelWrapper.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +import android.app.NotificationChannel; +import android.content.pm.ShortcutInfo; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * @hide + */ +public final class ConversationChannelWrapper implements Parcelable { + + private NotificationChannel mNotificationChannel; + private CharSequence mGroupLabel; + private CharSequence mParentChannelLabel; + private ShortcutInfo mShortcutInfo; + + public ConversationChannelWrapper() {} + + protected ConversationChannelWrapper(Parcel in) { + mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader()); + mGroupLabel = in.readCharSequence(); + mParentChannelLabel = in.readCharSequence(); + mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader()); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mNotificationChannel, flags); + dest.writeCharSequence(mGroupLabel); + dest.writeCharSequence(mParentChannelLabel); + dest.writeParcelable(mShortcutInfo, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<ConversationChannelWrapper> CREATOR = + new Creator<ConversationChannelWrapper>() { + @Override + public ConversationChannelWrapper createFromParcel(Parcel in) { + return new ConversationChannelWrapper(in); + } + + @Override + public ConversationChannelWrapper[] newArray(int size) { + return new ConversationChannelWrapper[size]; + } + }; + + + public NotificationChannel getNotificationChannel() { + return mNotificationChannel; + } + + public void setNotificationChannel( + NotificationChannel notificationChannel) { + mNotificationChannel = notificationChannel; + } + + public CharSequence getGroupLabel() { + return mGroupLabel; + } + + public void setGroupLabel(CharSequence groupLabel) { + mGroupLabel = groupLabel; + } + + public CharSequence getParentChannelLabel() { + return mParentChannelLabel; + } + + public void setParentChannelLabel(CharSequence parentChannelLabel) { + mParentChannelLabel = parentChannelLabel; + } + + public ShortcutInfo getShortcutInfo() { + return mShortcutInfo; + } + + public void setShortcutInfo(ShortcutInfo shortcutInfo) { + mShortcutInfo = shortcutInfo; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ConversationChannelWrapper that = (ConversationChannelWrapper) o; + return Objects.equals(getNotificationChannel(), that.getNotificationChannel()) && + Objects.equals(getGroupLabel(), that.getGroupLabel()) && + Objects.equals(getParentChannelLabel(), that.getParentChannelLabel()) && + Objects.equals(getShortcutInfo(), that.getShortcutInfo()); + } + + @Override + public int hashCode() { + return Objects.hash(getNotificationChannel(), getGroupLabel(), getParentChannelLabel(), + getShortcutInfo()); + } +} diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index e053ed58a82b..0ff2e0386121 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -214,6 +214,32 @@ public abstract class NotificationListenerService extends Service { public static final int REASON_TIMEOUT = 19; /** + * @hide + */ + @IntDef(prefix = "REASON_", value = { + REASON_CLICK, + REASON_CANCEL, + REASON_CANCEL_ALL, + REASON_ERROR, + REASON_PACKAGE_CHANGED, + REASON_USER_STOPPED, + REASON_PACKAGE_BANNED, + REASON_APP_CANCEL, + REASON_APP_CANCEL_ALL, + REASON_LISTENER_CANCEL, + REASON_LISTENER_CANCEL_ALL, + REASON_GROUP_SUMMARY_CANCELED, + REASON_GROUP_OPTIMIZATION, + REASON_PACKAGE_SUSPENDED, + REASON_PROFILE_TURNED_OFF, + REASON_UNAUTOBUNDLED, + REASON_CHANNEL_BANNED, + REASON_SNOOZED, + REASON_TIMEOUT + }) + public @interface NotificationCancelReason{}; + + /** * The full trim of the StatusBarNotification including all its features. * * @hide diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index 531ade04fe02..e65bd9f20ec4 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -412,23 +412,23 @@ public class PhoneStateListener { * domain. This indication does not necessarily indicate a change of service state, which should * be tracked via {@link #LISTEN_SERVICE_STATE}. * - * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling - * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or + * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). * * @see #onRegistrationFailed() */ - @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000; /** * Listen for Barring Information for the current registered / camped cell. * - * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling - * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or + * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). * * @see #onBarringInfoChanged() */ - @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int LISTEN_BARRING_INFO = 0x80000000; /* diff --git a/core/java/android/view/CutoutSpecification.java b/core/java/android/view/CutoutSpecification.java new file mode 100644 index 000000000000..d21a9520e12c --- /dev/null +++ b/core/java/android/view/CutoutSpecification.java @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static android.view.Gravity.BOTTOM; +import static android.view.Gravity.LEFT; +import static android.view.Gravity.RIGHT; +import static android.view.Gravity.TOP; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Insets; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.text.TextUtils; +import android.util.Log; +import android.util.PathParser; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Locale; +import java.util.Objects; + +/** + * In order to accept the cutout specification for all of edges in devices, the specification + * parsing method is extracted from + * {@link android.view.DisplayCutout#fromResourcesRectApproximation(Resources, int, int)} to be + * the specified class for parsing the specification. + * BNF definition: + * <ul> + * <li>Cutouts Specification = ([Cutout Delimiter],Cutout Specification) {...}, [Dp] ; </li> + * <li>Cutout Specification = [Vertical Position], (SVG Path Element), [Horizontal Position] + * [Bind Cutout] ;</li> + * <li>Vertical Position = "@bottom" | "@center_vertical" ;</li> + * <li>Horizontal Position = "@left" | "@right" ;</li> + * <li>Bind Cutout = "@bind_left_cutout" | "@bind_right_cutout" ;</li> + * <li>Cutout Delimiter = "@cutout" ;</li> + * <li>Dp = "@dp"</li> + * </ul> + * + * <ul> + * <li>Vertical position is top by default if there is neither "@bottom" nor "@center_vertical" + * </li> + * <li>Horizontal position is center horizontal by default if there is neither "@left" nor + * "@right".</li> + * <li>@bottom make the cutout piece bind to bottom edge.</li> + * <li>both of @bind_left_cutout and @bind_right_cutout are use to claim the cutout belong to + * left or right edge cutout.</li> + * </ul> + * + * @hide + */ +@VisibleForTesting(visibility = PACKAGE) +public class CutoutSpecification { + private static final String TAG = "CutoutSpecification"; + private static final boolean DEBUG = false; + + private static final int MINIMAL_ACCEPTABLE_PATH_LENGTH = "H1V1Z".length(); + + private static final char MARKER_START_CHAR = '@'; + private static final String DP_MARKER = MARKER_START_CHAR + "dp"; + + private static final String BOTTOM_MARKER = MARKER_START_CHAR + "bottom"; + private static final String RIGHT_MARKER = MARKER_START_CHAR + "right"; + private static final String LEFT_MARKER = MARKER_START_CHAR + "left"; + private static final String CUTOUT_MARKER = MARKER_START_CHAR + "cutout"; + private static final String CENTER_VERTICAL_MARKER = MARKER_START_CHAR + "center_vertical"; + + /* By default, it's top bound cutout. That's why TOP_BOUND_CUTOUT_MARKER is not defined */ + private static final String BIND_RIGHT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_right_cutout"; + private static final String BIND_LEFT_CUTOUT_MARKER = MARKER_START_CHAR + "bind_left_cutout"; + + private final Path mPath; + private final Rect mLeftBound; + private final Rect mTopBound; + private final Rect mRightBound; + private final Rect mBottomBound; + private final Insets mInsets; + + private CutoutSpecification(@NonNull Parser parser) { + mPath = parser.mPath; + mLeftBound = parser.mLeftBound; + mTopBound = parser.mTopBound; + mRightBound = parser.mRightBound; + mBottomBound = parser.mBottomBound; + mInsets = parser.mInsets; + + if (DEBUG) { + Log.d(TAG, String.format(Locale.ENGLISH, + "left cutout = %s, top cutout = %s, right cutout = %s, bottom cutout = %s", + mLeftBound != null ? mLeftBound.toString() : "", + mTopBound != null ? mTopBound.toString() : "", + mRightBound != null ? mRightBound.toString() : "", + mBottomBound != null ? mBottomBound.toString() : "")); + } + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Path getPath() { + return mPath; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getLeftBound() { + return mLeftBound; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getTopBound() { + return mTopBound; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getRightBound() { + return mRightBound; + } + + @VisibleForTesting(visibility = PACKAGE) + @Nullable + public Rect getBottomBound() { + return mBottomBound; + } + + /** + * To count the safe inset according to the cutout bounds and waterfall inset. + * + * @return the safe inset. + */ + @VisibleForTesting(visibility = PACKAGE) + @NonNull + public Rect getSafeInset() { + return mInsets.toRect(); + } + + private static int decideWhichEdge(boolean isTopEdgeShortEdge, + boolean isShortEdge, boolean isStart) { + return (isTopEdgeShortEdge) + ? ((isShortEdge) ? (isStart ? TOP : BOTTOM) : (isStart ? LEFT : RIGHT)) + : ((isShortEdge) ? (isStart ? LEFT : RIGHT) : (isStart ? TOP : BOTTOM)); + } + + /** + * The CutoutSpecification Parser. + */ + @VisibleForTesting(visibility = PACKAGE) + public static class Parser { + private final boolean mIsShortEdgeOnTop; + private final float mDensity; + private final int mDisplayWidth; + private final int mDisplayHeight; + private final Matrix mMatrix; + private Insets mInsets; + private int mSafeInsetLeft; + private int mSafeInsetTop; + private int mSafeInsetRight; + private int mSafeInsetBottom; + + private final Rect mTmpRect = new Rect(); + private final RectF mTmpRectF = new RectF(); + + private boolean mInDp; + + private Path mPath; + private Rect mLeftBound; + private Rect mTopBound; + private Rect mRightBound; + private Rect mBottomBound; + + private boolean mPositionFromLeft = false; + private boolean mPositionFromRight = false; + private boolean mPositionFromBottom = false; + private boolean mPositionFromCenterVertical = false; + + private boolean mBindLeftCutout = false; + private boolean mBindRightCutout = false; + private boolean mBindBottomCutout = false; + + private boolean mIsTouchShortEdgeStart; + private boolean mIsTouchShortEdgeEnd; + private boolean mIsCloserToStartSide; + + /** + * The constructor of the CutoutSpecification parser to parse the specification of cutout. + * @param density the display density. + * @param displayWidth the display width. + * @param displayHeight the display height. + */ + @VisibleForTesting(visibility = PACKAGE) + public Parser(float density, int displayWidth, int displayHeight) { + mDensity = density; + mDisplayWidth = displayWidth; + mDisplayHeight = displayHeight; + mMatrix = new Matrix(); + mIsShortEdgeOnTop = mDisplayWidth < mDisplayHeight; + } + + private void computeBoundsRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) { + mTmpRectF.setEmpty(); + p.computeBounds(mTmpRectF, false /* unused */); + mTmpRectF.round(inoutRect); + inoutRegion.op(inoutRect, Region.Op.UNION); + } + + private void resetStatus(StringBuilder sb) { + sb.setLength(0); + mPositionFromBottom = false; + mPositionFromLeft = false; + mPositionFromRight = false; + mPositionFromCenterVertical = false; + + mBindLeftCutout = false; + mBindRightCutout = false; + mBindBottomCutout = false; + } + + private void translateMatrix() { + final float offsetX; + if (mPositionFromRight) { + offsetX = mDisplayWidth; + } else if (mPositionFromLeft) { + offsetX = 0; + } else { + offsetX = mDisplayWidth / 2f; + } + + final float offsetY; + if (mPositionFromBottom) { + offsetY = mDisplayHeight; + } else if (mPositionFromCenterVertical) { + offsetY = mDisplayHeight / 2f; + } else { + offsetY = 0; + } + + mMatrix.reset(); + if (mInDp) { + mMatrix.postScale(mDensity, mDensity); + } + mMatrix.postTranslate(offsetX, offsetY); + } + + private int computeSafeInsets(int gravity, Rect rect) { + if (gravity == LEFT && rect.right > 0 && rect.right < mDisplayWidth) { + return rect.right; + } else if (gravity == TOP && rect.bottom > 0 && rect.bottom < mDisplayHeight) { + return rect.bottom; + } else if (gravity == RIGHT && rect.left > 0 && rect.left < mDisplayWidth) { + return mDisplayWidth - rect.left; + } else if (gravity == BOTTOM && rect.top > 0 && rect.top < mDisplayHeight) { + return mDisplayHeight - rect.top; + } + return 0; + } + + private void setSafeInset(int gravity, int inset) { + if (gravity == LEFT) { + mSafeInsetLeft = inset; + } else if (gravity == TOP) { + mSafeInsetTop = inset; + } else if (gravity == RIGHT) { + mSafeInsetRight = inset; + } else if (gravity == BOTTOM) { + mSafeInsetBottom = inset; + } + } + + private int getSafeInset(int gravity) { + if (gravity == LEFT) { + return mSafeInsetLeft; + } else if (gravity == TOP) { + return mSafeInsetTop; + } else if (gravity == RIGHT) { + return mSafeInsetRight; + } else if (gravity == BOTTOM) { + return mSafeInsetBottom; + } + return 0; + } + + @NonNull + private Rect onSetEdgeCutout(boolean isStart, boolean isShortEdge, @NonNull Rect rect) { + final int gravity; + if (isShortEdge) { + gravity = decideWhichEdge(mIsShortEdgeOnTop, true, isStart); + } else { + if (mIsTouchShortEdgeStart && mIsTouchShortEdgeEnd) { + gravity = decideWhichEdge(mIsShortEdgeOnTop, false, isStart); + } else if (mIsTouchShortEdgeStart || mIsTouchShortEdgeEnd) { + gravity = decideWhichEdge(mIsShortEdgeOnTop, true, + mIsCloserToStartSide); + } else { + gravity = decideWhichEdge(mIsShortEdgeOnTop, isShortEdge, isStart); + } + } + + int oldSafeInset = getSafeInset(gravity); + int newSafeInset = computeSafeInsets(gravity, rect); + if (oldSafeInset < newSafeInset) { + setSafeInset(gravity, newSafeInset); + } + + return new Rect(rect); + } + + private void setEdgeCutout(@NonNull Path newPath) { + if (mBindRightCutout && mRightBound == null) { + mRightBound = onSetEdgeCutout(false, !mIsShortEdgeOnTop, mTmpRect); + } else if (mBindLeftCutout && mLeftBound == null) { + mLeftBound = onSetEdgeCutout(true, !mIsShortEdgeOnTop, mTmpRect); + } else if (mBindBottomCutout && mBottomBound == null) { + mBottomBound = onSetEdgeCutout(false, mIsShortEdgeOnTop, mTmpRect); + } else if (!(mBindBottomCutout || mBindLeftCutout || mBindRightCutout) + && mTopBound == null) { + mTopBound = onSetEdgeCutout(true, mIsShortEdgeOnTop, mTmpRect); + } else { + return; + } + + if (mPath != null) { + mPath.addPath(newPath); + } else { + mPath = newPath; + } + } + + private void parseSvgPathSpec(Region region, String spec) { + if (TextUtils.length(spec) < MINIMAL_ACCEPTABLE_PATH_LENGTH) { + Log.e(TAG, "According to SVG definition, it shouldn't happen"); + return; + } + spec.trim(); + translateMatrix(); + + final Path newPath = PathParser.createPathFromPathData(spec); + newPath.transform(mMatrix); + computeBoundsRectAndAddToRegion(newPath, region, mTmpRect); + + if (DEBUG) { + Log.d(TAG, String.format(Locale.ENGLISH, + "hasLeft = %b, hasRight = %b, hasBottom = %b, hasCenterVertical = %b", + mPositionFromLeft, mPositionFromRight, mPositionFromBottom, + mPositionFromCenterVertical)); + Log.d(TAG, "region = " + region); + Log.d(TAG, "spec = \"" + spec + "\" rect = " + mTmpRect + " newPath = " + newPath); + } + + if (mTmpRect.isEmpty()) { + return; + } + + if (mIsShortEdgeOnTop) { + mIsTouchShortEdgeStart = mTmpRect.top <= 0; + mIsTouchShortEdgeEnd = mTmpRect.bottom >= mDisplayHeight; + mIsCloserToStartSide = mTmpRect.centerY() < mDisplayHeight / 2; + } else { + mIsTouchShortEdgeStart = mTmpRect.left <= 0; + mIsTouchShortEdgeEnd = mTmpRect.right >= mDisplayWidth; + mIsCloserToStartSide = mTmpRect.centerX() < mDisplayWidth / 2; + } + + setEdgeCutout(newPath); + } + + private void parseSpecWithoutDp(@NonNull String specWithoutDp) { + Region region = Region.obtain(); + StringBuilder sb = null; + int currentIndex = 0; + int lastIndex = 0; + while ((currentIndex = specWithoutDp.indexOf(MARKER_START_CHAR, lastIndex)) != -1) { + if (sb == null) { + sb = new StringBuilder(specWithoutDp.length()); + } + sb.append(specWithoutDp, lastIndex, currentIndex); + + if (specWithoutDp.startsWith(LEFT_MARKER, currentIndex)) { + if (!mPositionFromRight) { + mPositionFromLeft = true; + } + currentIndex += LEFT_MARKER.length(); + } else if (specWithoutDp.startsWith(RIGHT_MARKER, currentIndex)) { + if (!mPositionFromLeft) { + mPositionFromRight = true; + } + currentIndex += RIGHT_MARKER.length(); + } else if (specWithoutDp.startsWith(BOTTOM_MARKER, currentIndex)) { + if (!mPositionFromCenterVertical) { + parseSvgPathSpec(region, sb.toString()); + } + currentIndex += BOTTOM_MARKER.length(); + + /* prepare to parse the rest path */ + resetStatus(sb); + mBindBottomCutout = true; + mPositionFromBottom = true; + } else if (specWithoutDp.startsWith(CENTER_VERTICAL_MARKER, currentIndex)) { + if (!mPositionFromBottom) { + parseSvgPathSpec(region, sb.toString()); + } + currentIndex += CENTER_VERTICAL_MARKER.length(); + + /* prepare to parse the rest path */ + resetStatus(sb); + mPositionFromCenterVertical = true; + } else if (specWithoutDp.startsWith(CUTOUT_MARKER, currentIndex)) { + parseSvgPathSpec(region, sb.toString()); + currentIndex += CUTOUT_MARKER.length(); + + /* prepare to parse the rest path */ + resetStatus(sb); + } else if (specWithoutDp.startsWith(BIND_LEFT_CUTOUT_MARKER, currentIndex)) { + if (!mBindBottomCutout && !mBindRightCutout) { + mBindLeftCutout = true; + } + currentIndex += BIND_LEFT_CUTOUT_MARKER.length(); + } else if (specWithoutDp.startsWith(BIND_RIGHT_CUTOUT_MARKER, currentIndex)) { + if (!mBindBottomCutout && !mBindLeftCutout) { + mBindRightCutout = true; + } + currentIndex += BIND_RIGHT_CUTOUT_MARKER.length(); + } else { + currentIndex += 1; + } + + lastIndex = currentIndex; + } + + if (sb == null) { + parseSvgPathSpec(region, specWithoutDp); + } else { + sb.append(specWithoutDp, lastIndex, specWithoutDp.length()); + parseSvgPathSpec(region, sb.toString()); + } + + region.recycle(); + } + + /** + * To parse specification string as the CutoutSpecification. + * + * @param originalSpec the specification string + * @return the CutoutSpecification instance + */ + @VisibleForTesting(visibility = PACKAGE) + public CutoutSpecification parse(@NonNull String originalSpec) { + Objects.requireNonNull(originalSpec); + + int dpIndex = originalSpec.lastIndexOf(DP_MARKER); + mInDp = (dpIndex != -1); + final String spec; + if (dpIndex != -1) { + spec = originalSpec.substring(0, dpIndex) + + originalSpec.substring(dpIndex + DP_MARKER.length()); + } else { + spec = originalSpec; + } + + parseSpecWithoutDp(spec); + + mInsets = Insets.of(mSafeInsetLeft, mSafeInsetTop, mSafeInsetRight, mSafeInsetBottom); + return new CutoutSpecification(this); + } + } +} diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index d433591f2b3c..31fc16188814 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -31,18 +31,12 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.Resources; import android.graphics.Insets; -import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.graphics.Region.Op; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; -import android.util.Log; import android.util.Pair; -import android.util.PathParser; import android.util.proto.ProtoOutputStream; import com.android.internal.R; @@ -63,10 +57,6 @@ import java.util.List; public final class DisplayCutout { private static final String TAG = "DisplayCutout"; - private static final String BOTTOM_MARKER = "@bottom"; - private static final String DP_MARKER = "@dp"; - private static final String RIGHT_MARKER = "@right"; - private static final String LEFT_MARKER = "@left"; /** * Category for overlays that allow emulating a display cutout on devices that don't have @@ -703,77 +693,16 @@ public final class DisplayCutout { } } - Path p = null; - Rect boundTop = null; - Rect boundBottom = null; - Rect safeInset = new Rect(); - String bottomSpec = null; - if (!TextUtils.isEmpty(spec)) { - spec = spec.trim(); - final float offsetX; - if (spec.endsWith(RIGHT_MARKER)) { - offsetX = displayWidth; - spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim(); - } else if (spec.endsWith(LEFT_MARKER)) { - offsetX = 0; - spec = spec.substring(0, spec.length() - LEFT_MARKER.length()).trim(); - } else { - offsetX = displayWidth / 2f; - } - final boolean inDp = spec.endsWith(DP_MARKER); - if (inDp) { - spec = spec.substring(0, spec.length() - DP_MARKER.length()); - } - - if (spec.contains(BOTTOM_MARKER)) { - String[] splits = spec.split(BOTTOM_MARKER, 2); - spec = splits[0].trim(); - bottomSpec = splits[1].trim(); - } + spec = spec.trim(); - final Matrix m = new Matrix(); - final Region r = Region.obtain(); - if (!spec.isEmpty()) { - try { - p = PathParser.createPathFromPathData(spec); - } catch (Throwable e) { - Log.wtf(TAG, "Could not inflate cutout: ", e); - } - - if (p != null) { - if (inDp) { - m.postScale(density, density); - } - m.postTranslate(offsetX, 0); - p.transform(m); + CutoutSpecification cutoutSpec = new CutoutSpecification.Parser(density, + displayWidth, displayHeight).parse(spec); + Rect safeInset = cutoutSpec.getSafeInset(); + final Rect boundLeft = cutoutSpec.getLeftBound(); + final Rect boundTop = cutoutSpec.getTopBound(); + final Rect boundRight = cutoutSpec.getRightBound(); + final Rect boundBottom = cutoutSpec.getBottomBound(); - boundTop = new Rect(); - toRectAndAddToRegion(p, r, boundTop); - safeInset.top = boundTop.bottom; - } - } - - if (bottomSpec != null) { - int bottomInset = 0; - Path bottomPath = null; - try { - bottomPath = PathParser.createPathFromPathData(bottomSpec); - } catch (Throwable e) { - Log.wtf(TAG, "Could not inflate bottom cutout: ", e); - } - - if (bottomPath != null) { - // Keep top transform - m.postTranslate(0, displayHeight); - bottomPath.transform(m); - p.addPath(bottomPath); - boundBottom = new Rect(); - toRectAndAddToRegion(bottomPath, r, boundBottom); - bottomInset = displayHeight - boundBottom.top; - } - safeInset.bottom = bottomInset; - } - } if (!waterfallInsets.equals(Insets.NONE)) { safeInset.set( @@ -784,9 +713,9 @@ public final class DisplayCutout { } final DisplayCutout cutout = new DisplayCutout( - safeInset, waterfallInsets, null /* boundLeft */, boundTop, - null /* boundRight */, boundBottom, false /* copyArguments */); - final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout); + safeInset, waterfallInsets, boundLeft, boundTop, + boundRight, boundBottom, false /* copyArguments */); + final Pair<Path, DisplayCutout> result = new Pair<>(cutoutSpec.getPath(), cutout); synchronized (CACHE_LOCK) { sCachedSpec = spec; sCachedDisplayWidth = displayWidth; @@ -798,14 +727,6 @@ public final class DisplayCutout { return result; } - private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) { - final RectF rectF = new RectF(); - p.computeBounds(rectF, false /* unused */); - rectF.round(inoutRect); - inoutRegion.op(inoutRect, Op.UNION); - } - - private static Insets loadWaterfallInset(Resources res) { return Insets.of( res.getDimensionPixelSize(R.dimen.waterfall_display_left_edge_size), diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index f9a023fafc3c..17303478fa50 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -97,6 +97,8 @@ interface IWindowManager IWindowSession openSession(in IWindowSessionCallback callback); + boolean useBLAST(); + @UnsupportedAppUsage void getInitialDisplaySize(int displayId, out Point size); @UnsupportedAppUsage diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index d4961eab7473..6caa4fed6409 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -198,7 +198,7 @@ public class InsetsSource implements Parcelable { return "InsetsSource: {" + "mType=" + InsetsState.typeToString(mType) + ", mFrame=" + mFrame.toShortString() - + ", mVisible" + mVisible + + ", mVisible=" + mVisible + "}"; } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 78a080de20e5..4ac6a666a21b 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -258,14 +258,14 @@ public class Surface implements Parcelable { */ public void release() { synchronized (mLock) { - if (mNativeObject != 0) { - nativeRelease(mNativeObject); - setNativeObjectLocked(0); - } if (mHwuiContext != null) { mHwuiContext.destroy(); mHwuiContext = null; } + if (mNativeObject != 0) { + nativeRelease(mNativeObject); + setNativeObjectLocked(0); + } } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index b1c354f6f717..637a088e4c5d 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -22,7 +22,6 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAY import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.CompatibilityInfo.Translator; @@ -43,8 +42,8 @@ import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceControl.Transaction; -import android.view.accessibility.AccessibilityNodeInfo; import android.view.SurfaceControlViewHost; +import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.view.SurfaceCallbackHelper; @@ -386,7 +385,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * This gets called on a RenderThread worker thread, so members accessed here must * be protected by a lock. */ - final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; + final boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST(); viewRoot.registerRtFrameCallback(frame -> { try { final SurfaceControl.Transaction t = useBLAST ? @@ -1120,7 +1119,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t, Rect position, long frameNumber) { - if (frameNumber > 0 && !WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (frameNumber > 0 && !WindowManagerGlobal.getInstance().useBLAST()) { final ViewRootImpl viewRoot = getViewRootImpl(); t.deferTransactionUntil(surface, viewRoot.getRenderSurfaceControl(), @@ -1138,7 +1137,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private void setParentSpaceRectangle(Rect position, long frameNumber) { - final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; + final boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST(); final ViewRootImpl viewRoot = getViewRootImpl(); final SurfaceControl.Transaction t = useBLAST ? viewRoot.getBLASTSyncTransaction() : mRtTransaction; @@ -1199,7 +1198,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionLost(long frameNumber) { - boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; + boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST(); if (DEBUG) { Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", System.identityHashCode(this), frameNumber)); @@ -1538,7 +1537,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void invalidate(boolean invalidateCache) { super.invalidate(invalidateCache); - if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (!WindowManagerGlobal.getInstance().useBLAST()) { return; } final ViewRootImpl viewRoot = getViewRootImpl(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a407bd8f001e..a6f8fad817e1 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -12729,12 +12729,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return findViewInsideOutShouldExist(root, mNextFocusForwardId); case FOCUS_BACKWARD: { if (mID == View.NO_ID) return null; - return root.findViewByPredicateInsideOut(this, new Predicate<View>() { - @Override - public boolean test(View t) { - return t.findViewById(t.mNextFocusForwardId) == View.this; - } - }); + final View rootView = root; + final View startView = this; + // Since we have forward links but no backward links, we need to find the view that + // forward links to this view. We can't just find the view with the specified ID + // because view IDs need not be unique throughout the tree. + return root.findViewByPredicateInsideOut(startView, + t -> findViewInsideOutShouldExist(rootView, t, t.mNextFocusForwardId) + == startView); } } return null; @@ -12764,11 +12766,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private View findViewInsideOutShouldExist(View root, int id) { + return findViewInsideOutShouldExist(root, this, id); + } + + private View findViewInsideOutShouldExist(View root, View start, int id) { if (mMatchIdPredicate == null) { mMatchIdPredicate = new MatchIdPredicate(); } mMatchIdPredicate.mId = id; - View result = root.findViewByPredicateInsideOut(this, mMatchIdPredicate); + View result = root.findViewByPredicateInsideOut(start, mMatchIdPredicate); if (result == null) { Log.w(VIEW_LOG_TAG, "couldn't find view with id " + id); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 2c30d29d8da2..ebfe66f7e7b7 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -222,26 +222,26 @@ public final class ViewRootImpl implements ViewParent, * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_NONE = 0; + public static int sNewInsetsMode = + SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0); /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_IME = 1; + public static final int NEW_INSETS_MODE_NONE = 0; /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static final int NEW_INSETS_MODE_FULL = 2; + public static final int NEW_INSETS_MODE_IME = 1; /** * @see #USE_NEW_INSETS_PROPERTY * @hide */ - public static int sNewInsetsMode = - SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, NEW_INSETS_MODE_IME); + public static final int NEW_INSETS_MODE_FULL = 2; /** * Set this system property to true to force the view hierarchy to render @@ -315,6 +315,8 @@ public final class ViewRootImpl implements ViewParent, */ private boolean mForceNextConfigUpdate; + private final boolean mUseBLASTAdapter; + /** * Signals that compatibility booleans have been initialized according to * target SDK versions. @@ -734,6 +736,7 @@ public final class ViewRootImpl implements ViewParent, loadSystemProperties(); mImeFocusController = new ImeFocusController(this); + mUseBLASTAdapter = WindowManagerGlobal.getInstance().useBLAST(); } public static void addFirstDrawHandler(Runnable callback) { @@ -861,7 +864,7 @@ public final class ViewRootImpl implements ViewParent, if (mWindowAttributes.packageName == null) { mWindowAttributes.packageName = mBasePackageName; } - if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (mUseBLASTAdapter) { mWindowAttributes.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; } @@ -1341,7 +1344,7 @@ public final class ViewRootImpl implements ViewParent, } mWindowAttributes.privateFlags |= compatibleWindowFlag; - if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (mUseBLASTAdapter) { mWindowAttributes.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; } @@ -7342,7 +7345,7 @@ public final class ViewRootImpl implements ViewParent, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize, mBlastSurfaceControl); if (mSurfaceControl.isValid()) { - if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (!mUseBLASTAdapter) { mSurface.copyFrom(mSurfaceControl); } else { mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x, @@ -9537,7 +9540,7 @@ public final class ViewRootImpl implements ViewParent, } SurfaceControl getRenderSurfaceControl() { - if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (mUseBLASTAdapter) { return mBlastSurfaceControl; } else { return mSurfaceControl; diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index f501de91b3b7..ea8e73885980 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -16,8 +16,6 @@ package android.view; -import static android.view.WindowInsets.Type.ime; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -148,7 +146,7 @@ public interface WindowInsetsController { * @param types The {@link InsetsType}s the application has requested to control. * @param durationMillis Duration of animation in * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the - * animation doesn't have a predetermined duration.T his value will be + * animation doesn't have a predetermined duration. This value will be * passed to {@link InsetsAnimation#getDurationMillis()} * @param interpolator The interpolator used for this animation, or {@code null} if this * animation doesn't follow an interpolation curve. This value will be diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index f03c4e731283..c22b8921390c 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -56,13 +56,7 @@ import java.util.ArrayList; public final class WindowManagerGlobal { private static final String TAG = "WindowManager"; - private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter"; - - /** - * This flag controls whether ViewRootImpl will utilize the Blast Adapter - * to send buffer updates to SurfaceFlinger - */ - public static final boolean USE_BLAST_ADAPTER = false; + private final boolean mUseBLASTAdapter; /** * The user is navigating with keys (not the touch screen), so @@ -165,6 +159,11 @@ public final class WindowManagerGlobal { private Runnable mSystemPropertyUpdater; private WindowManagerGlobal() { + try { + mUseBLASTAdapter = getWindowManagerService().useBLAST(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } @UnsupportedAppUsage @@ -233,6 +232,13 @@ public final class WindowManagerGlobal { } } + /** + * Whether or not to use BLAST for ViewRootImpl + */ + public boolean useBLAST() { + return mUseBLASTAdapter; + } + @UnsupportedAppUsage public String[] getViewRootNames() { synchronized (mLock) { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 4365d1f5194a..56683dd9a5d1 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -277,7 +277,7 @@ public final class WindowManagerImpl implements WindowManager { .setStableInsets(Insets.of(stableInsets)) .setDisplayCutout(displayCutout.get()).build(); } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } - return null; } } diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index c80a1ae55228..7f90d572ce89 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -33,11 +33,11 @@ import android.util.Printer; import android.view.View; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.Objects; /** * An EditorInfo describes several attributes of a text editing object @@ -558,7 +558,7 @@ public class EditorInfo implements InputType, Parcelable { * editor wants to trim out the first 10 chars, subTextStart should be 10. */ public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) { - Preconditions.checkNotNull(subText); + Objects.requireNonNull(subText); // Swap selection start and end if necessary. final int subTextSelStart = initialSelStart > initialSelEnd @@ -585,25 +585,35 @@ public class EditorInfo implements InputType, Parcelable { return; } - // The input text is too long. Let's try to trim it reasonably. Fundamental rules are: - // 1. Text before the cursor is the most important information to IMEs. - // 2. Text after the cursor is the second important information to IMEs. - // 3. Selected text is the least important information but it shall NEVER be truncated. - // When it is too long, just drop it. - // - // Source: <TextBeforeCursor><Selection><TextAfterCursor> - // Possible results: - // 1. <(maybeTrimmedAtHead)TextBeforeCursor><Selection><TextAfterCursor(maybeTrimmedAtTail)> - // 2. <(maybeTrimmedAtHead)TextBeforeCursor><TextAfterCursor(maybeTrimmedAtTail)> - // - final int sourceSelLength = subTextSelEnd - subTextSelStart; + trimLongSurroundingText(subText, subTextSelStart, subTextSelEnd); + } + + /** + * Trims the initial surrounding text when it is over sized. Fundamental trimming rules are: + * - The text before the cursor is the most important information to IMEs. + * - The text after the cursor is the second important information to IMEs. + * - The selected text is the least important information but it shall NEVER be truncated. When + * it is too long, just drop it. + *<p><pre> + * For example, the subText can be viewed as + * TextBeforeCursor + Selection + TextAfterCursor + * The result could be + * 1. (maybeTrimmedAtHead)TextBeforeCursor + Selection + TextAfterCursor(maybeTrimmedAtTail) + * 2. (maybeTrimmedAtHead)TextBeforeCursor + TextAfterCursor(maybeTrimmedAtTail)</pre> + * + * @param subText The long text that needs to be trimmed. + * @param selStart The text offset of the start of the selection. + * @param selEnd The text offset of the end of the selection + */ + private void trimLongSurroundingText(CharSequence subText, int selStart, int selEnd) { + final int sourceSelLength = selEnd - selStart; // When the selected text is too long, drop it. final int newSelLength = (sourceSelLength > MAX_INITIAL_SELECTION_LENGTH) ? 0 : sourceSelLength; // Distribute rest of length quota to TextBeforeCursor and TextAfterCursor in 4:1 ratio. - final int subTextBeforeCursorLength = subTextSelStart; - final int subTextAfterCursorLength = subTextLength - subTextSelEnd; + final int subTextBeforeCursorLength = selStart; + final int subTextAfterCursorLength = subText.length() - selEnd; final int maxLengthMinusSelection = MEMORY_EFFICIENT_TEXT_LENGTH - newSelLength; final int possibleMaxBeforeCursorLength = Math.min(subTextBeforeCursorLength, (int) (0.8 * maxLengthMinusSelection)); @@ -617,24 +627,23 @@ public class EditorInfo implements InputType, Parcelable { // We don't want to cut surrogate pairs in the middle. Exam that at the new head and tail. if (isCutOnSurrogate(subText, - subTextSelStart - newBeforeCursorLength, TrimPolicy.HEAD)) { + selStart - newBeforeCursorLength, TrimPolicy.HEAD)) { newBeforeCursorHead = newBeforeCursorHead + 1; newBeforeCursorLength = newBeforeCursorLength - 1; } if (isCutOnSurrogate(subText, - subTextSelEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) { + selEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) { newAfterCursorLength = newAfterCursorLength - 1; } // Now we know where to trim, compose the initialSurroundingText. final int newTextLength = newBeforeCursorLength + newSelLength + newAfterCursorLength; - CharSequence newInitialSurroundingText; + final CharSequence newInitialSurroundingText; if (newSelLength != sourceSelLength) { final CharSequence beforeCursor = subText.subSequence(newBeforeCursorHead, newBeforeCursorHead + newBeforeCursorLength); - - final CharSequence afterCursor = subText.subSequence(subTextSelEnd, - subTextSelEnd + newAfterCursorLength); + final CharSequence afterCursor = subText.subSequence(selEnd, + selEnd + newAfterCursorLength); newInitialSurroundingText = TextUtils.concat(beforeCursor, afterCursor); } else { @@ -651,15 +660,16 @@ public class EditorInfo implements InputType, Parcelable { } /** - * Get <var>n</var> characters of text before the current cursor position. May be {@code null} - * when the protocol is not supported. + * Get <var>length</var> characters of text before the current cursor position. May be + * {@code null} when the protocol is not supported. * * @param length The expected length of the text. * @param flags Supplies additional options controlling how the text is returned. May be * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}. * @return the text before the cursor position; the length of the returned text might be less - * than <var>n</var>. When there is no text before the cursor, an empty string will be returned. - * It could also be {@code null} when the editor or system could not support this protocol. + * than <var>length</var>. When there is no text before the cursor, an empty string will be + * returned. It could also be {@code null} when the editor or system could not support this + * protocol. */ @Nullable public CharSequence getInitialTextBeforeCursor(int length, int flags) { @@ -667,8 +677,8 @@ public class EditorInfo implements InputType, Parcelable { } /** - * Gets the selected text, if any. May be {@code null} when no text is selected or the selected - * text is way too long. + * Gets the selected text, if any. May be {@code null} when the protocol is not supported or the + * selected text is way too long. * * @param flags Supplies additional options controlling how the text is returned. May be * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}. @@ -693,15 +703,16 @@ public class EditorInfo implements InputType, Parcelable { } /** - * Get <var>n</var> characters of text after the current cursor position. May be {@code null} - * when the protocol is not supported. + * Get <var>length</var> characters of text after the current cursor position. May be + * {@code null} when the protocol is not supported. * * @param length The expected length of the text. * @param flags Supplies additional options controlling how the text is returned. May be * either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}. * @return the text after the cursor position; the length of the returned text might be less - * than <var>n</var>. When there is no text after the cursor, an empty string will be returned. - * It could also be {@code null} when the editor or system could not support this protocol. + * than <var>length</var>. When there is no text after the cursor, an empty string will be + * returned. It could also be {@code null} when the editor or system could not support this + * protocol. */ @Nullable public CharSequence getInitialTextAfterCursor(int length, int flags) { @@ -863,7 +874,6 @@ public class EditorInfo implements InputType, Parcelable { return 0; } - // TODO(b/148035211): Unit tests for this class static final class InitialSurroundingText implements Parcelable { @Nullable final CharSequence mSurroundingText; final int mSelectionHead; diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index f0c16aadf12f..dbab81b129a9 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -664,8 +664,6 @@ public final class InputMethodManager { */ @Override public void setCurrentRootView(ViewRootImpl rootView) { - // If the mCurRootView is losing window focus, release the strong reference to it - // so as not to prevent it from being garbage-collected. if (mWindowFocusGainFuture != null) { mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */); mWindowFocusGainFuture = null; diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java index 4329a206746d..fed3dbf8f49c 100644 --- a/core/java/android/view/textclassifier/TextClassificationSession.java +++ b/core/java/android/view/textclassifier/TextClassificationSession.java @@ -16,6 +16,7 @@ package android.view.textclassifier; +import android.annotation.NonNull; import android.annotation.WorkerThread; import android.view.textclassifier.SelectionEvent.InvocationMethod; @@ -23,6 +24,8 @@ import com.android.internal.util.Preconditions; import java.util.Objects; +import sun.misc.Cleaner; + /** * Session-aware TextClassifier. */ @@ -35,6 +38,7 @@ final class TextClassificationSession implements TextClassifier { private final SelectionEventHelper mEventHelper; private final TextClassificationSessionId mSessionId; private final TextClassificationContext mClassificationContext; + private final Cleaner mCleaner; private boolean mDestroyed; @@ -44,6 +48,8 @@ final class TextClassificationSession implements TextClassifier { mSessionId = new TextClassificationSessionId(); mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext); initializeRemoteSession(); + // This ensures destroy() is called if the client forgot to do so. + mCleaner = Cleaner.create(this, new CleanerRunnable(mEventHelper, mDelegate)); } @Override @@ -114,8 +120,7 @@ final class TextClassificationSession implements TextClassifier { @Override public void destroy() { - mEventHelper.endSession(); - mDelegate.destroy(); + mCleaner.clean(); mDestroyed = true; } @@ -258,4 +263,25 @@ final class TextClassificationSession implements TextClassifier { } } } + + // We use a static nested class here to avoid retaining the object reference of the outer + // class. Otherwise. the Cleaner would never be triggered. + private static class CleanerRunnable implements Runnable { + @NonNull + private final SelectionEventHelper mEventHelper; + @NonNull + private final TextClassifier mDelegate; + + CleanerRunnable( + @NonNull SelectionEventHelper eventHelper, @NonNull TextClassifier delegate) { + mEventHelper = Objects.requireNonNull(eventHelper); + mDelegate = Objects.requireNonNull(delegate); + } + + @Override + public void run() { + mEventHelper.endSession(); + mDelegate.destroy(); + } + } } diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.java b/core/java/android/view/textclassifier/TextClassificationSessionId.java index f90e6b2e407c..0b6fba225280 100644 --- a/core/java/android/view/textclassifier/TextClassificationSessionId.java +++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java @@ -17,6 +17,8 @@ package android.view.textclassifier; import android.annotation.NonNull; +import android.os.Binder; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -28,7 +30,10 @@ import java.util.UUID; * This class represents the id of a text classification session. */ public final class TextClassificationSessionId implements Parcelable { - private final @NonNull String mValue; + @NonNull + private final String mValue; + @NonNull + private final IBinder mToken; /** * Creates a new instance. @@ -36,7 +41,7 @@ public final class TextClassificationSessionId implements Parcelable { * @hide */ public TextClassificationSessionId() { - this(UUID.randomUUID().toString()); + this(UUID.randomUUID().toString(), new Binder()); } /** @@ -46,34 +51,28 @@ public final class TextClassificationSessionId implements Parcelable { * * @hide */ - public TextClassificationSessionId(@NonNull String value) { - mValue = value; + public TextClassificationSessionId(@NonNull String value, @NonNull IBinder token) { + mValue = Objects.requireNonNull(value); + mToken = Objects.requireNonNull(token); + } + + /** @hide */ + @NonNull + public IBinder getToken() { + return mToken; } @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + mValue.hashCode(); - return result; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TextClassificationSessionId that = (TextClassificationSessionId) o; + return Objects.equals(mValue, that.mValue) && Objects.equals(mToken, that.mToken); } @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - TextClassificationSessionId other = (TextClassificationSessionId) obj; - if (!mValue.equals(other.mValue)) { - return false; - } - return true; + public int hashCode() { + return Objects.hash(mValue, mToken); } @Override @@ -84,6 +83,7 @@ public final class TextClassificationSessionId implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mValue); + parcel.writeStrongBinder(mToken); } @Override @@ -96,28 +96,18 @@ public final class TextClassificationSessionId implements Parcelable { * * @return The flattened id. */ - public @NonNull String flattenToString() { + @NonNull + public String flattenToString() { return mValue; } - /** - * Unflattens a print job id from a string. - * - * @param string The string. - * @return The unflattened id, or null if the string is malformed. - * - * @hide - */ - public static @NonNull TextClassificationSessionId unflattenFromString(@NonNull String string) { - return new TextClassificationSessionId(string); - } - - public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationSessionId> CREATOR = + @NonNull + public static final Parcelable.Creator<TextClassificationSessionId> CREATOR = new Parcelable.Creator<TextClassificationSessionId>() { @Override public TextClassificationSessionId createFromParcel(Parcel parcel) { return new TextClassificationSessionId( - Objects.requireNonNull(parcel.readString())); + parcel.readString(), parcel.readStrongBinder()); } @Override diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index a2c70b9afbee..8c52d1fadc52 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -263,9 +263,14 @@ public class Toast { /** * Return the view. * - * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps - * targeting API level {@link Build.VERSION_CODES#R} or higher that haven't called {@link - * #setView(View)} with a non-{@code null} view, this method will return {@code null}. + * <p>Toasts constructed with {@link #Toast(Context)} that haven't called {@link #setView(View)} + * with a non-{@code null} view will return {@code null} here. + * + * <p>Starting from Android {@link Build.VERSION_CODES#R}, in apps targeting API level {@link + * Build.VERSION_CODES#R} or higher, toasts constructed with {@link #makeText(Context, + * CharSequence, int)} or its variants will also return {@code null} here unless they had called + * {@link #setView(View)} with a non-{@code null} view. If you want to be notified when the + * toast is shown or hidden, use {@link #addCallback(Callback)}. * * @see #setView * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the @@ -276,7 +281,7 @@ public class Toast { * will not have custom toast views displayed. */ @Deprecated - public View getView() { + @Nullable public View getView() { return mNextView; } diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index a0de51d4d211..0ccc45e000e6 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -25,6 +25,7 @@ import static android.system.OsConstants.S_IRWXU; import static android.system.OsConstants.S_IXGRP; import static android.system.OsConstants.S_IXOTH; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; @@ -32,7 +33,12 @@ import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.parsing.AndroidPackage; import android.os.Build; +import android.os.IBinder; import android.os.SELinux; +import android.os.ServiceManager; +import android.os.incremental.IIncrementalService; +import android.os.incremental.IncrementalManager; +import android.os.incremental.IncrementalStorage; import android.system.ErrnoException; import android.system.Os; import android.util.Slog; @@ -44,6 +50,7 @@ import java.io.Closeable; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; +import java.nio.file.Path; import java.util.List; /** @@ -481,8 +488,54 @@ public class NativeLibraryHelper { */ private static int incrementalConfigureNativeBinariesForSupportedAbi(Handle handle, File libSubDir, String abi) { - // TODO(b/136132412): implement this - return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + final String[] apkPaths = handle.apkPaths; + if (apkPaths == null || apkPaths.length == 0) { + Slog.e(TAG, "No apks to extract native libraries from."); + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } + + final IBinder incrementalService = ServiceManager.getService(Context.INCREMENTAL_SERVICE); + if (incrementalService == null) { + //TODO(b/133435829): add incremental specific error codes + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } + final IncrementalManager incrementalManager = new IncrementalManager( + IIncrementalService.Stub.asInterface(incrementalService)); + final File apkParent = new File(apkPaths[0]).getParentFile(); + IncrementalStorage incrementalStorage = + incrementalManager.openStorage(apkParent.getAbsolutePath()); + if (incrementalStorage == null) { + Slog.e(TAG, "Failed to find incremental storage"); + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } + + String libRelativeDir = getRelativePath(apkParent, libSubDir); + if (libRelativeDir == null) { + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } + + for (int i = 0; i < apkPaths.length; i++) { + if (!incrementalStorage.configureNativeBinaries(apkPaths[i], libRelativeDir, abi)) { + return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; + } + } + return PackageManager.INSTALL_SUCCEEDED; + } + + private static String getRelativePath(File base, File target) { + try { + final Path basePath = base.toPath(); + final Path targetPath = target.toPath(); + final Path relativePath = basePath.relativize(targetPath); + if (relativePath.toString().isEmpty()) { + return ""; + } + return relativePath.toString(); + } catch (IllegalArgumentException ex) { + Slog.e(TAG, "Failed to find relative path between: " + base.getAbsolutePath() + + " and: " + target.getAbsolutePath()); + return null; + } } // We don't care about the other return values for now. diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java index 48d2bc2ae58d..67ffd4d93404 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -56,8 +56,8 @@ public interface UiEventLogger { * @param event an enum implementing UiEventEnum interface. * @param uid the uid of the relevant app, if known (0 otherwise). * @param packageName the package name of the relevant app, if known (null otherwise). - * @param instance An identifier obtained from an InstanceIdSequence. + * @param instance An identifier obtained from an InstanceIdSequence. If null, reduces to log(). */ void logWithInstanceId(@NonNull UiEventEnum event, int uid, @Nullable String packageName, - @NonNull InstanceId instance); + @Nullable InstanceId instance); } diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index 785b2edf2e1b..4d171ec8a3a8 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -41,9 +41,11 @@ public class UiEventLoggerImpl implements UiEventLogger { public void logWithInstanceId(UiEventEnum event, int uid, String packageName, InstanceId instance) { final int eventID = event.getId(); - if (eventID > 0) { + if ((eventID > 0) && (instance != null)) { FrameworkStatsLog.write(FrameworkStatsLog.UI_EVENT_REPORTED, eventID, uid, packageName, instance.getId()); + } else { + log(event, uid, packageName); } } } diff --git a/core/java/com/android/internal/policy/TaskResizingAlgorithm.java b/core/java/com/android/internal/policy/TaskResizingAlgorithm.java new file mode 100644 index 000000000000..1fce098fb93e --- /dev/null +++ b/core/java/com/android/internal/policy/TaskResizingAlgorithm.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy; + +import android.annotation.IntDef; +import android.graphics.Point; +import android.graphics.Rect; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Given a move coordinate (x, y), the original taks bounds and relevant details, calculate the new + * bounds. + * + * @hide + */ +public class TaskResizingAlgorithm { + + @IntDef(flag = true, + value = { + CTRL_NONE, + CTRL_LEFT, + CTRL_RIGHT, + CTRL_TOP, + CTRL_BOTTOM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CtrlType {} + + public static final int CTRL_NONE = 0x0; + public static final int CTRL_LEFT = 0x1; + public static final int CTRL_RIGHT = 0x2; + public static final int CTRL_TOP = 0x4; + public static final int CTRL_BOTTOM = 0x8; + + // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait). + // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever + // aspect he desires. + @VisibleForTesting + public static final float MIN_ASPECT = 1.2f; + + /** + * Given a (x, y) point and its original starting down point and its original bounds, calculate + * and return a new resized bound. + * @param x the new moved X point. + * @param y the new moved Y point. + * @param startDragX the original starting X point. + * @param startDragY the original starting Y point. + * @param originalBounds the original bound before resize. + * @param ctrlType The type of resize operation. + * @param minVisibleWidth The minimal width required for the new size. + * @param minVisibleHeight The minimal height required for the new size. + * @param maxVisibleSize The maximum size allowed. + * @param preserveOrientation + * @param startOrientationWasLandscape + * @return + */ + public static Rect resizeDrag(float x, float y, float startDragX, float startDragY, + Rect originalBounds, int ctrlType, int minVisibleWidth, int minVisibleHeight, + Point maxVisibleSize, boolean preserveOrientation, + boolean startOrientationWasLandscape) { + // This is a resizing operation. + // We need to keep various constraints: + // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y] + // 2. The orientation is kept - if required. + final int deltaX = Math.round(x - startDragX); + final int deltaY = Math.round(y - startDragY); + int left = originalBounds.left; + int top = originalBounds.top; + int right = originalBounds.right; + int bottom = originalBounds.bottom; + + // Calculate the resulting width and height of the drag operation. + int width = right - left; + int height = bottom - top; + if ((ctrlType & CTRL_LEFT) != 0) { + width = Math.max(minVisibleWidth, width - deltaX); + } else if ((ctrlType & CTRL_RIGHT) != 0) { + width = Math.max(minVisibleWidth, width + deltaX); + } + if ((ctrlType & CTRL_TOP) != 0) { + height = Math.max(minVisibleHeight, height - deltaY); + } else if ((ctrlType & CTRL_BOTTOM) != 0) { + height = Math.max(minVisibleHeight, height + deltaY); + } + + // If we have to preserve the orientation - check that we are doing so. + final float aspect = (float) width / (float) height; + if (preserveOrientation && ((startOrientationWasLandscape && aspect < MIN_ASPECT) + || (!startOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) { + // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major + // drag axis. What ever is producing the bigger rectangle will be chosen. + int width1; + int width2; + int height1; + int height2; + if (startOrientationWasLandscape) { + // Assuming that the width is our target we calculate the height. + width1 = Math.max(minVisibleWidth, Math.min(maxVisibleSize.x, width)); + height1 = Math.min(height, Math.round((float) width1 / MIN_ASPECT)); + if (height1 < minVisibleHeight) { + // If the resulting height is too small we adjust to the minimal size. + height1 = minVisibleHeight; + width1 = Math.max(minVisibleWidth, + Math.min(maxVisibleSize.x, Math.round((float) height1 * MIN_ASPECT))); + } + // Assuming that the height is our target we calculate the width. + height2 = Math.max(minVisibleHeight, Math.min(maxVisibleSize.y, height)); + width2 = Math.max(width, Math.round((float) height2 * MIN_ASPECT)); + if (width2 < minVisibleWidth) { + // If the resulting width is too small we adjust to the minimal size. + width2 = minVisibleWidth; + height2 = Math.max(minVisibleHeight, + Math.min(maxVisibleSize.y, Math.round((float) width2 / MIN_ASPECT))); + } + } else { + // Assuming that the width is our target we calculate the height. + width1 = Math.max(minVisibleWidth, Math.min(maxVisibleSize.x, width)); + height1 = Math.max(height, Math.round((float) width1 * MIN_ASPECT)); + if (height1 < minVisibleHeight) { + // If the resulting height is too small we adjust to the minimal size. + height1 = minVisibleHeight; + width1 = Math.max(minVisibleWidth, + Math.min(maxVisibleSize.x, Math.round((float) height1 / MIN_ASPECT))); + } + // Assuming that the height is our target we calculate the width. + height2 = Math.max(minVisibleHeight, Math.min(maxVisibleSize.y, height)); + width2 = Math.min(width, Math.round((float) height2 / MIN_ASPECT)); + if (width2 < minVisibleWidth) { + // If the resulting width is too small we adjust to the minimal size. + width2 = minVisibleWidth; + height2 = Math.max(minVisibleHeight, + Math.min(maxVisibleSize.y, Math.round((float) width2 * MIN_ASPECT))); + } + } + + // Use the bigger of the two rectangles if the major change was positive, otherwise + // do the opposite. + final boolean grows = width > (right - left) || height > (bottom - top); + if (grows == (width1 * height1 > width2 * height2)) { + width = width1; + height = height1; + } else { + width = width2; + height = height2; + } + } + + // Generate the final bounds by keeping the opposite drag edge constant. + if ((ctrlType & CTRL_LEFT) != 0) { + left = right - width; + } else { // Note: The right might have changed - if we pulled at the right or not. + right = left + width; + } + if ((ctrlType & CTRL_TOP) != 0) { + top = bottom - height; + } else { // Note: The height might have changed - if we pulled at the bottom or not. + bottom = top + height; + } + return new Rect(left, top, right, bottom); + } +} diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 26d2f19b13b6..2fcc1de776fb 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -903,7 +903,6 @@ public class SystemConfig { } break; case "component-override": { readComponentOverrides(parser, permFile); - XmlUtils.skipCurrentTag(parser); } break; case "backup-transport-whitelisted-service": { if (allowFeatures) { @@ -1403,8 +1402,7 @@ public class SystemConfig { final int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { - String name = parser.getName(); - if ("component".equals(name)) { + if ("component".equals(parser.getName())) { String clsname = parser.getAttributeValue(null, "class"); String enabled = parser.getAttributeValue(null, "enabled"); if (clsname == null) { @@ -1432,8 +1430,6 @@ public class SystemConfig { } componentEnabledStates.put(clsname, !"false".equals(enabled)); - } else { - XmlUtils.skipCurrentTag(parser); } } } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 3d0926d61789..8959d6fb845e 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -182,6 +182,7 @@ cc_library_shared { "android_hardware_UsbRequest.cpp", "android_hardware_location_ActivityRecognitionHardware.cpp", "android_util_FileObserver.cpp", + "android/graphics/GraphicsStatsService.cpp", "android/graphics/SurfaceTexture.cpp", "android/opengl/poly_clip.cpp", // TODO: .arm "android/opengl/util.cpp", @@ -273,6 +274,7 @@ cc_library_shared { "libstats_jni", "libstatslog", "server_configurable_flags", + "libstatspull", ], export_shared_lib_headers: [ // AndroidRuntime.h depends on nativehelper/jni.h @@ -358,7 +360,6 @@ cc_library_static { "android_view_RenderNode.cpp", "android_util_PathParser.cpp", - "android/graphics/AnimatedImageDrawable.cpp", "android/graphics/Bitmap.cpp", "android/graphics/BitmapFactory.cpp", "android/graphics/ByteBufferStreamAdaptor.cpp", @@ -434,6 +435,7 @@ cc_library_static { "android_view_TextureLayer.cpp", "android_view_ThreadedRenderer.cpp", + "android/graphics/AnimatedImageDrawable.cpp", "android/graphics/BitmapRegionDecoder.cpp", "android/graphics/GIFMovie.cpp", "android/graphics/Movie.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index d0e8fd355199..b47b7e39d1e8 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -209,9 +209,11 @@ static const char* kNoGenerationalCCRuntimeOption = "-Xgc:nogenerational_cc"; static const char* PROFILE_BOOT_CLASS_PATH = "profilebootclasspath"; // Feature flag name for running the JIT in Zygote experiment, b/119800099. -static const char* ENABLE_APEX_IMAGE = "enable_apex_image"; -// Flag to pass to the runtime when using the apex image. -static const char* kApexImageOption = "-Ximage:/system/framework/apex.art"; +// TODO: Rename the server-level flag or remove. +static const char* ENABLE_JITZYGOTE_IMAGE = "enable_apex_image"; +// Flag to pass to the runtime when using the JIT Zygote image. +static const char* kJitZygoteImageOption = + "-Ximage:boot.art:/nonx/boot-framework.art!/system/etc/boot-image.prof"; // Feature flag name for disabling lock profiling. static const char* DISABLE_LOCK_PROFILING = "disable_lock_profiling"; @@ -689,16 +691,16 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p addOption("-Xjitsaveprofilinginfo"); } - std::string use_apex_image_flag = - server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE, - ENABLE_APEX_IMAGE, - /*default_value=*/ ""); + std::string use_jitzygote_image_flag = + server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE, + ENABLE_JITZYGOTE_IMAGE, + /*default_value=*/""); // Use the APEX boot image for boot class path profiling to get JIT samples on BCP methods. // Also use the APEX boot image if it's explicitly enabled via configuration flag. - const bool use_apex_image = profile_boot_class_path || (use_apex_image_flag == "true"); + const bool use_apex_image = profile_boot_class_path || (use_jitzygote_image_flag == "true"); if (use_apex_image) { - addOption(kApexImageOption); - ALOGI("Using Apex boot image: '%s'\n", kApexImageOption); + ALOGI("Using JIT Zygote image: '%s'\n", kJitZygoteImageOption); + addOption(kJitZygoteImageOption); } else if (parseRuntimeOption("dalvik.vm.boot-image", bootImageBuf, "-Ximage:")) { ALOGI("Using dalvik.vm.boot-image: '%s'\n", bootImageBuf); } else { diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 571a33879411..c49c3a0f6b61 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -40,6 +40,7 @@ extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env); extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); extern int register_android_graphics_Graphics(JNIEnv* env); extern int register_android_graphics_ImageDecoder(JNIEnv*); +extern int register_android_graphics_Interpolator(JNIEnv* env); extern int register_android_graphics_MaskFilter(JNIEnv* env); extern int register_android_graphics_NinePatch(JNIEnv*); extern int register_android_graphics_PathEffect(JNIEnv* env); @@ -115,6 +116,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)}, {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)}, {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)}, + {"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)}, {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)}, {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)}, {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)}, diff --git a/core/jni/android/graphics/GraphicsStatsService.cpp b/core/jni/android/graphics/GraphicsStatsService.cpp new file mode 100644 index 000000000000..ef0aacc4d9ec --- /dev/null +++ b/core/jni/android/graphics/GraphicsStatsService.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "GraphicsStatsService" + +#include <JankTracker.h> +#include <jni.h> +#include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <nativehelper/ScopedUtfChars.h> +#include <service/GraphicsStatsService.h> +#include <stats_event.h> +#include <stats_pull_atom_callback.h> +#include <statslog.h> +#include "core_jni_helpers.h" + +namespace android { + +using namespace android::uirenderer; + +static jint getAshmemSize(JNIEnv*, jobject) { + return sizeof(ProfileData); +} + +static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) { + GraphicsStatsService::Dump* dump = + GraphicsStatsService::createDump(fd, + isProto ? GraphicsStatsService::DumpType::Protobuf + : GraphicsStatsService::DumpType::Text); + return reinterpret_cast<jlong>(dump); +} + +static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage, + jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { + std::string path; + const ProfileData* data = nullptr; + LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null"); + ScopedByteArrayRO buffer{env}; + if (jdata != nullptr) { + buffer.reset(jdata); + LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), + "Buffer size %zu doesn't match expected %zu!", buffer.size(), + sizeof(ProfileData)); + data = reinterpret_cast<const ProfileData*>(buffer.get()); + } + if (jpath != nullptr) { + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), + "Failed to get path chars"); + path.assign(pathChars.c_str(), pathChars.size()); + } + ScopedUtfChars packageChars(env, jpackage); + LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), + "Failed to get path chars"); + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer"); + + const std::string package(packageChars.c_str(), packageChars.size()); + GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data); +} + +static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) { + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); + const std::string path(pathChars.c_str(), pathChars.size()); + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + GraphicsStatsService::addToDump(dump, path); +} + +static void finishDump(JNIEnv*, jobject, jlong dumpPtr) { + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + GraphicsStatsService::finishDump(dump); +} + +static void finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr, jlong pulledData, + jboolean lastFullDay) { + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + AStatsEventList* data = reinterpret_cast<AStatsEventList*>(pulledData); + GraphicsStatsService::finishDumpInMemory(dump, data, lastFullDay == JNI_TRUE); +} + +static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, + jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { + ScopedByteArrayRO buffer(env, jdata); + LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), + "Buffer size %zu doesn't match expected %zu!", buffer.size(), + sizeof(ProfileData)); + ScopedUtfChars pathChars(env, jpath); + LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); + ScopedUtfChars packageChars(env, jpackage); + LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), + "Failed to get path chars"); + + const std::string path(pathChars.c_str(), pathChars.size()); + const std::string package(packageChars.c_str(), packageChars.size()); + const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get()); + GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); +} + +static jobject gGraphicsStatsServiceObject = nullptr; +static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID; + +static JNIEnv* getJNIEnv() { + JavaVM* vm = AndroidRuntime::getJavaVM(); + JNIEnv* env = nullptr; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); + } + } + return env; +} + +// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. +static AStatsManager_PullAtomCallbackReturn graphicsStatsPullCallback(int32_t atom_tag, + AStatsEventList* data, + void* cookie) { + JNIEnv* env = getJNIEnv(); + if (!env) { + return false; + } + if (gGraphicsStatsServiceObject == nullptr) { + ALOGE("Failed to get graphicsstats service"); + return AStatsManager_PULL_SKIP; + } + + for (bool lastFullDay : {true, false}) { + env->CallVoidMethod(gGraphicsStatsServiceObject, + gGraphicsStatsService_pullGraphicsStatsMethodID, + (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE), + reinterpret_cast<jlong>(data)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + ALOGE("Failed to invoke graphicsstats service"); + return AStatsManager_PULL_SKIP; + } + } + return AStatsManager_PULL_SUCCESS; +} + +// Register a puller for GRAPHICS_STATS atom with the statsd service. +static void nativeInit(JNIEnv* env, jobject javaObject) { + gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject); + AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain(); + AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, 10 * 1000000); // 10 milliseconds + AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, 2 * NS_PER_SEC); // 2 seconds + + AStatsManager_registerPullAtomCallback(android::util::GRAPHICS_STATS, + &graphicsStatsPullCallback, metadata, nullptr); + + AStatsManager_PullAtomMetadata_release(metadata); +} + +static void nativeDestructor(JNIEnv* env, jobject javaObject) { + AStatsManager_unregisterPullAtomCallback(android::util::GRAPHICS_STATS); + env->DeleteGlobalRef(gGraphicsStatsServiceObject); + gGraphicsStatsServiceObject = nullptr; +} + +static const JNINativeMethod sMethods[] = + {{"nGetAshmemSize", "()I", (void*)getAshmemSize}, + {"nCreateDump", "(IZ)J", (void*)createDump}, + {"nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)addToDump}, + {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump}, + {"nFinishDump", "(J)V", (void*)finishDump}, + {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory}, + {"nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)saveBuffer}, + {"nativeInit", "()V", (void*)nativeInit}, + {"nativeDestructor", "()V", (void*)nativeDestructor}}; + +int register_android_graphics_GraphicsStatsService(JNIEnv* env) { + jclass graphicsStatsService_class = + FindClassOrDie(env, "android/graphics/GraphicsStatsService"); + gGraphicsStatsService_pullGraphicsStatsMethodID = + GetMethodIDOrDie(env, graphicsStatsService_class, "pullGraphicsStats", "(ZJ)V"); + return jniRegisterNativeMethods(env, "android/graphics/GraphicsStatsService", sMethods, + NELEM(sMethods)); +} + +} // namespace android diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp index d41e98241b07..44bff0188544 100644 --- a/core/jni/android_os_incremental_IncrementalManager.cpp +++ b/core/jni/android_os_incremental_IncrementalManager.cpp @@ -37,10 +37,28 @@ static jboolean nativeIsIncrementalPath(JNIEnv* env, return (jboolean)IncFs_IsIncFsPath(path.c_str()); } -static const JNINativeMethod method_table[] = { - {"nativeIsEnabled", "()Z", (void*)nativeIsEnabled}, - {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath}, -}; +static jbyteArray nativeUnsafeGetFileSignature(JNIEnv* env, jobject clazz, jstring javaPath) { + ScopedUtfChars path(env, javaPath); + + char signature[INCFS_MAX_SIGNATURE_SIZE]; + size_t size = sizeof(signature); + if (IncFs_UnsafeGetSignatureByPath(path.c_str(), signature, &size) < 0) { + return nullptr; + } + + jbyteArray result = env->NewByteArray(size); + if (result != nullptr) { + env->SetByteArrayRegion(result, 0, size, (const jbyte*)signature); + } + return result; +} + +static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled}, + {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", + (void*)nativeIsIncrementalPath}, + {"nativeUnsafeGetFileSignature", + "(Ljava/lang/String;)[B", + (void*)nativeUnsafeGetFileSignature}}; int register_android_os_incremental_IncrementalManager(JNIEnv* env) { return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager", diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto index e23f150fab25..6a4922000805 100644 --- a/core/proto/android/app/appexitinfo.proto +++ b/core/proto/android/app/appexitinfo.proto @@ -178,8 +178,8 @@ message ApplicationExitInfoProto { } optional Importance importance = 10; - optional int32 pss = 11; - optional int32 rss = 12; + optional int64 pss = 11; + optional int64 rss = 12; optional int64 timestamp = 13; optional string description = 14; } diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index c14d99c5d3fb..53aa2bbd57ea 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -708,6 +708,22 @@ enum Action { // ACTION: Deny "Access all files" for an app APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_DENY = 1731; + + // ACTION: Battery feature usage + ACTION_BATTERY_OPTION_FEATURE_USAGE = 1732; + + // ACTION: Battery feature runtime event + ACTION_BATTERY_OPTION_RUNTIME_EVENT = 1733; + + // ACTION: Settings > Developer Options > Toggle on Wireless debugging + // CATEGORY: SETTINGS + // OS: R + ACTION_ADB_WIRELESS_ON = 1734; + + // ACTION: Settings > Developer Options > Toggle off Wireless debugging + // CATEGORY: SETTINGS + // OS: R + ACTION_ADB_WIRELESS_OFF = 1735; } /** @@ -2581,4 +2597,24 @@ enum PageId { // OPEN: Settings -> Apps & Notifications -> Special App Access INTERACT_ACROSS_PROFILES = 1829; + + // OPEN: Settings > Notifications > (app or conversations) > conversation + NOTIFICATION_CONVERSATION_SETTINGS = 1830; + + // OPEN: Settings > Developer Options > Wireless debugging + // CATEGORY: SETTINGS + // OS: R + SETTINGS_ADB_WIRELESS = 1831; + + // OPEN: Settings > Developer Options > Wireless debugging + // > Pair device with pairing code > Pairing code dialog + // CATEGORY: SETTINGS + // OS: R + ADB_WIRELESS_DEVICE_PAIRING_DIALOG = 1832; + + // OPEN: Settings > Developer Options > Wireless debugging + // > Pair device with QR code > Scan QR code > Pairing device dialog + // CATEGORY: SETTINGS + // OS: R + ADB_WIRELESS_DEVICE_QR_PAIRING_DIALOG = 1833; } diff --git a/core/proto/android/content/locusid.proto b/core/proto/android/content/locusid.proto new file mode 100644 index 000000000000..4f0ce6b19e70 --- /dev/null +++ b/core/proto/android/content/locusid.proto @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.content; + +option java_multiple_files = true; + +// On disk representation of android.content.LocusId. Currently used by +// com.android.server.people.ConversationInfoProto. +message LocusIdProto { + optional string locus_id = 1; +} diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index 303d62dbb30a..546c5a092a50 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -219,12 +219,8 @@ message ConstantsProto { // The maximum number of background jobs we allow when the system is in a // critical memory state. optional int32 bg_critical_job_count = 14; - // The maximum number of times we allow a job to have itself rescheduled - // before giving up on it, for standard jobs. - optional int32 max_standard_reschedule_count = 15; - // The maximum number of times we allow a job to have itself rescheduled - // before giving up on it, for jobs that are executing work. - optional int32 max_work_reschedule_count = 16; + reserved 15; // max_standard_reschedule_count + reserved 16; // max_work_reschedule_count // The minimum backoff time to allow for linear backoff. optional int64 min_linear_backoff_time_ms = 17; // The minimum backoff time to allow for exponential backoff. diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto new file mode 100644 index 000000000000..294b6efd3cbe --- /dev/null +++ b/core/proto/android/server/peopleservice.proto @@ -0,0 +1,75 @@ +/* + * 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.server.people; + +option java_multiple_files = true; + +import "frameworks/base/core/proto/android/content/locusid.proto"; + +// On disk data of conversation infos for a user and app package. +message ConversationInfosProto { + + // The series of conversation infos for a user and app package. + repeated ConversationInfoProto conversation_infos = 1; +} + +// Individual conversation info (com.android.server.people.data.ConversationInfo) for a user +// and app package. +message ConversationInfoProto { + + // The conversation's shortcut id. + optional string shortcut_id = 1; + + // The conversation's locus id. + optional .android.content.LocusIdProto locus_id_proto = 2; + + // The URI of the contact in the conversation. + optional string contact_uri = 3; + + // The notification channel id of the conversation. + optional string notification_channel_id = 4; + + // Integer representation of shortcut bit flags. + optional int32 shortcut_flags = 5; + + // Integer representation of conversation bit flags. + optional int32 conversation_flags = 6; +} + +// Individual event (com.android.server.people.data.Event). +message PeopleEventProto { + + // For valid values, refer to java class documentation. + optional int32 event_type = 1; + + optional int64 time = 2; + + // The duration of the event. Should only be set for some event_types. Refer to java class + // documentation for details. + optional int32 duration = 3; +} + +// Index of events' time distributions (com.android.server.people.data.EventIndex). +message PeopleEventIndexProto { + // Each long value in event_bitmaps represents a time slot, there should be 4 values. Further + // details can be found in class documentation. + repeated int64 event_bitmaps = 1; + + optional int64 last_updated_time = 2; +} diff --git a/core/proto/android/service/OWNERS b/core/proto/android/service/OWNERS new file mode 100644 index 000000000000..70cb50f75362 --- /dev/null +++ b/core/proto/android/service/OWNERS @@ -0,0 +1 @@ +per-file sensor_service.proto = arthuri@google.com, bduddie@google.com, stange@google.com diff --git a/core/proto/android/service/sensor_service.proto b/core/proto/android/service/sensor_service.proto index 8598f86a7f28..48f6670b59fe 100644 --- a/core/proto/android/service/sensor_service.proto +++ b/core/proto/android/service/sensor_service.proto @@ -39,7 +39,7 @@ message SensorServiceProto { OP_MODE_RESTRICTED = 2; OP_MODE_DATA_INJECTION = 3; } - + optional sint32 init_status = 16; optional int64 current_time_ms = 1; optional SensorDeviceProto sensor_device = 2; optional SensorListProto sensors = 3; @@ -56,6 +56,8 @@ message SensorServiceProto { repeated SensorEventConnectionProto active_connections = 13; repeated SensorDirectConnectionProto direct_connections = 14; repeated SensorRegistrationInfoProto previous_registrations = 15; + + // Next tag: 17 } // Proto dump of android::SensorDevice diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6d63a532ae14..71a42e454f30 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -96,6 +96,7 @@ <protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" /> <protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" /> <protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" /> + <protected-broadcast android:name="android.intent.action.LOAD_DATA" /> <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" /> <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" /> @@ -1535,7 +1536,7 @@ @hide --> <permission android:name="android.permission.ACCESS_CONTEXT_HUB" - android:protectionLevel="signature" /> + android:protectionLevel="signature|privileged" /> <uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB"/> <!-- @SystemApi Allows an application to create mock location providers for testing. @@ -2376,6 +2377,7 @@ @hide --> <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" android:protectionLevel="signature|installer|telephony" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <!-- Allows interaction across profiles in the same profile group. --> <permission android:name="android.permission.INTERACT_ACROSS_PROFILES" diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml index 46e8f64753ac..b01eb3944b50 100644 --- a/core/res/res/drawable-nodpi/platlogo.xml +++ b/core/res/res/drawable-nodpi/platlogo.xml @@ -1,31 +1,50 @@ -<!-- -Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="240dp" - android:height="240dp" - android:viewportWidth="24" - android:viewportHeight="24"> + android:width="512dp" + android:height="512dp" + android:viewportWidth="512" + android:viewportHeight="512"> <path - android:fillColor="#000" - android:pathData="M16 4c-2.2 0-4 1.8-4 4v4H4V8c0-2.2 1.8-4 4-4h8z"/> + android:fillColor="#F86734" + android:pathData="M416.23 236.62h-10.67c-1.46 0-2.65-1.19-2.65-2.65v-9.85c0-1.47 1.19-2.65 2.65-2.65h23.37c1.47 0 2.66 1.19 2.66 2.65v66.9c0 1.46-1.2 2.65-2.66 2.65H418.9c-1.47 0-2.66-1.19-2.66-2.65v-54.4z"/> <path - android:fillColor="#000" - android:pathData="M8 20c2.2 0 4-1.8 4-4v-4H4v8h4z"/> + android:fillColor="#F86734" + android:pathData="M455.51 236.62h-10.67c-1.47 0-2.65-1.19-2.65-2.65v-9.85c0-1.47 1.18-2.65 2.65-2.65h23.37c1.47 0 2.66 1.19 2.66 2.65v66.9c0 1.46-1.2 2.65-2.66 2.65h-10.05c-1.46 0-2.65-1.19-2.65-2.65v-54.4z"/> <path - android:fillColor="#80000000" - android:pathData="M16 12c2.2 0 4-1.8 4-4V4h-8v8h4z"/> + android:fillColor="#D6F0FF" + android:pathData="M364.12 400.25a4.34 4.34 0 1 0 0 8.68a4.34 4.34 0 1 0 0-8.68z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M275.46 433.53a4.84 4.84 0 1 0 0 9.68a4.84 4.84 0 1 0 0-9.68z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M184.52 418.83a5.36 5.36 0 1 0 0 10.72a5.36 5.36 0 1 0 0-10.72z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M110.42 359.19a5.89 5.89 0 1 0 0 11.78a5.89 5.89 0 1 0 0-11.78z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M75.94 270.17a6.43 6.43 0 1 0 0 12.86a6.43 6.43 0 1 0 0-12.86z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M89.48 178.57a6.98 6.98 0 1 0 0 13.96a6.98 6.98 0 1 0 0-13.96z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M147.97 103.54a7.54 7.54 0 1 0 0 15.08a7.54 7.54 0 1 0 0-15.08z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M236.63 66.7a8.1 8.1 0 1 0 0 16.2a8.1 8.1 0 1 0 0-16.2z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M327.09 78.3a8.66 8.66 0 1 0 0 17.32a8.66 8.66 0 1 0 0-17.32z"/> + <path + android:fillColor="#D6F0FF" + android:pathData="M401.05 136.97a9.22 9.22 0 1 0 0 18.44a9.22 9.22 0 1 0 0-18.44z"/> + <group> + <path + android:fillColor="#3DDB85" + android:pathData="M255.45 129.46a128.11 128.11 0 1 0 0 256.22a128.11 128.11 0 1 0 0-256.22z"/> + <path + android:fillColor="#FFF" + android:pathData="M339.23 236.09a21.48 21.48 0 1 0 0 42.96a21.48 21.48 0 1 0 0-42.96z"/> + </group> </vector> - diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml index 0e9aab2ab59d..700781b1d0b1 100644 --- a/core/res/res/drawable-nodpi/stat_sys_adb.xml +++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml @@ -1,17 +1,17 @@ -<!-- -Copyright (C) 2020 The Android Open Source Project +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2020 The Android Open Source Project - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" @@ -19,12 +19,22 @@ Copyright (C) 2020 The Android Open Source Project android:viewportWidth="24" android:viewportHeight="24"> <path - android:fillColor="#000" - android:pathData="M16 4c-2.2 0-4 1.8-4 4v4H4V8c0-2.2 1.8-4 4-4h8z"/> - <path - android:fillColor="#000" - android:pathData="M8 20c2.2 0 4-1.8 4-4v-4H4v8h4z"/> - <path - android:fillColor="#80000000" - android:pathData="M16 12c2.2 0 4-1.8 4-4V4h-8v8h4z"/> + android:fillColor="#FFFFFF" + android:pathData=" +M 12,1 +A 11 11 0 0 0 1,12 +A 11 11 0 1 0 12,1 +Z + +M 12.5,8 +a 3,3 0 0 1 6,0 +a 3,3 0 0 1 -6,0 +Z + +M 5.5,8 +a 3,3 0 0 1 6,0 +l 0,8 +a 3,3 0 0 1 -6,0 +Z +"/> </vector> diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index d6c5a1379a58..5e895336d7f3 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Skakel werkprofiel aan?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Jou werkprogramme, kennisgewings, data en ander werkprofielkenmerke sal aangeskakel word"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Skakel aan"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Program is nie beskikbaar nie"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is nie op die oomblik beskikbaar nie."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Hierdie program is vir \'n ouer weergawe van Android gebou en sal dalk nie behoorlik werk nie. Probeer kyk vir opdaterings, of kontak die ontwikkelaar."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kyk vir opdatering"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Jy het nuwe boodskappe"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index e9409fbf92ed..b637a637c3fe 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"የስራ መገለጫ ይብራ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"የእርስዎ የስራ መተግበሪያዎች፣ ማሳወቂያዎች፣ ውሂብ እና ሌሎች የስራ መገለጫ ባህሪያት ይበራሉ"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"አብራ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"መተግበሪያ አይገኝም"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> አሁን አይገኝም።"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ይህ መተግበሪያ ለቆየ የAndroid ስሪት ነው የተገነባው፣ እና በአግባቡ ላይሰራ ይችላል። ዝማኔዎች ካሉ ለመመልከት ይሞክሩ፣ ወይም ደግሞ ገንቢውን ያነጋግሩ።"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ዝማኔ ካለ አረጋግጥ"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"አዲስ መልዕክቶች አለዎት"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 23ffd7b6594a..002d61e0ff80 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -915,7 +915,7 @@ <string name="factorytest_no_action" msgid="339252838115675515">"لم يتم العثور على أي حزمة توفر إجراء FACTORY_TEST."</string> <string name="factorytest_reboot" msgid="2050147445567257365">"إعادة تشغيل"</string> <string name="js_dialog_title" msgid="7464775045615023241">"تعرض الصفحة في \"<xliff:g id="TITLE">%s</xliff:g>\":"</string> - <string name="js_dialog_title_default" msgid="3769524569903332476">"جافا سكريبت"</string> + <string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string> <string name="js_dialog_before_unload_title" msgid="7012587995876771246">"تأكيد الانتقال"</string> <string name="js_dialog_before_unload_positive_button" msgid="4274257182303565509">"مغادرة هذه الصفحة"</string> <string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"البقاء في هذه الصفحة"</string> @@ -1395,7 +1395,7 @@ <string name="test_harness_mode_notification_message" msgid="3039123743127958420">"يمكنك إجراء إعادة ضبط على الإعدادات الأصلية لإيقاف وضع \"مفعِّل اختبار\"."</string> <string name="console_running_notification_title" msgid="6087888939261635904">"وحدة التحكّم التسلسلية مفعّلة"</string> <string name="console_running_notification_message" msgid="7892751888125174039">"الأداء متأثر. لإيقاف وحدة التحكّم، تحقّق من برنامج الإقلاع."</string> - <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"السوائل والشوائب في منفذ USB"</string> + <string name="usb_contaminant_detected_title" msgid="4359048603069159678">"السوائل أو الشوائب في منفذ USB"</string> <string name="usb_contaminant_detected_message" msgid="7346100585390795743">"تمّ إيقاف منفذ USB تلقائيًا. انقُر لمعرفة المزيد من المعلومات."</string> <string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"مسموح باستخدام منفذ USB"</string> <string name="usb_contaminant_not_detected_message" msgid="892863190942660462">"لم يَعُد الهاتف يكتشف سوائل أو شوائب."</string> @@ -1981,6 +1981,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"تفعيل الملف الشخصي للعمل؟"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"سيتم تفعيل تطبيقات العمل التي تستخدمها والإشعارات والبيانات وغيرها من ميزات الملف الشخصي للعمل"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"تشغيل"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"التطبيق غير متاح"</string> + <string name="app_blocked_message" msgid="542972921087873023">"تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> غير متاح الآن."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"تمّ إنشاء هذا التطبيق لإصدار قديم من Android وقد لا يعمل بشكل صحيح. جرِّب البحث عن تحديثات أو الاتصال بمطوّر البرامج."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"البحث عن تحديث"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"لديك رسائل جديدة"</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index a09c377d45b9..bdf8fcf18718 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"কৰ্মস্থানৰ প্ৰ\'ফাইল অন কৰিবনে?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"আপোনাৰ কৰ্মস্থানৰ এপসমূহ, জাননীসমূহ, ডেটা আৰু কৰ্মস্থানৰ প্ৰ\'ফাইলৰ অইন সুবিধাসমূহ অন কৰা হ\'ব"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"অন কৰক"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"এপ্টো উপলব্ধ নহয়"</string> + <string name="app_blocked_message" msgid="542972921087873023">"এই মুহূৰ্তত <xliff:g id="APP_NAME">%1$s</xliff:g> উপলব্ধ নহয়।"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই এপটো Androidৰ এটা পুৰণা সংস্কৰণৰ বাবে প্ৰস্তুত কৰা হৈছিল, আৰু ই বিচৰাধৰণে কাম নকৰিবও পাৰে। ইয়াৰ আপডে’ট আছে নেকি চাওক, বা বিকাশকৰ্তাৰ সৈতে যোগাযোগ কৰক।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"আপডে’ট আছে নেকি চাওক"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"আপুনি নতুন বার্তা লাভ কৰিছে"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index b1209b100633..6f13024ada2e 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"İş profili aktiv edilsin?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"İş tətbiqləri, bildirişləri, data və digər iş profili funksiyaları aktiv ediləcək"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivləşdirin"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Tətbiq əlçatan deyil"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> hazırda əlçatan deyil."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu tətbiq köhnə Android versiyası üçün hazırlanıb və düzgün işləməyə bilər. Güncəlləməni yoxlayın və ya developer ilə əlaqə saxlayın."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Güncəlləməni yoxlayın"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Yeni mesajlarınız var"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 5600882ca16f..862495a6f9bf 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -1885,6 +1885,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Da uključimo profil za Work?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Uključiće se poslovne aplikacije, obaveštenja, podaci i druge funkcije profila za Work"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova aplikacija je napravljena za stariju verziju Android-a, pa možda neće raditi ispravno. Potražite ažuriranja ili kontaktirajte programera."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Potraži ažuriranje"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index fb80e4b043b9..8a02da2f3794 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Уключыць працоўны профіль?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Будуць уключаны вашы рабочыя праграмы, апавяшчэнні, даныя і іншыя функцыі працоўнага профілю"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Уключыць"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Праграма недаступная"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" цяпер недаступная."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Гэта праграма была створана для больш старой версіі Android і можа не працаваць належным чынам. Праверце наяўнасць абнаўленняў або звярніцеся да распрацоўшчыка."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Праверыць на наяўнасць абнаўленняў"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"У вас ёсць новыя паведамленні"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 2892853f0923..f7830c738dcc 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Вкл. на служ. потр. профил?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Служебните ви приложения, известия и данни, както и другите функции на служебния потребителски профил ще бъдат включени"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Включване"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Приложението не е достъпно"</string> + <string name="app_blocked_message" msgid="542972921087873023">"В момента няма достъп до <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Това приложение бе създадено за по-стара версия на Android и може да не работи правилно. Опитайте да проверите за актуализации или се свържете с програмиста."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверка за актуализация"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нови съобщения"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 4c95081cc15d..af50b3357cee 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -1297,7 +1297,7 @@ <string name="no_permissions" msgid="5729199278862516390">"কোনো অনুমতির প্রয়োজন নেই"</string> <string name="perm_costs_money" msgid="749054595022779685">"এর জন্য অর্থপ্রদান করতে হতে পারে"</string> <string name="dlg_ok" msgid="5103447663504839312">"ঠিক আছে"</string> - <string name="usb_charging_notification_title" msgid="1674124518282666955">"এই ডিভাইসটি USB এর মাধ্যমে চার্জ করুন"</string> + <string name="usb_charging_notification_title" msgid="1674124518282666955">"এই ডিভাইসটি USB দিয়ে চার্জ করা হচ্ছে"</string> <string name="usb_supplying_notification_title" msgid="5378546632408101811">"সংযুক্ত ডিভাইসটি USB এর মাধ্যমে চার্জ করা হচ্ছে"</string> <string name="usb_mtp_notification_title" msgid="1065989144124499810">"USB ফাইল ট্রান্সফার চালু করা হয়েছে"</string> <string name="usb_ptp_notification_title" msgid="5043437571863443281">"USB এর মাধ্যমে PTP চালু করা হয়েছে"</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"কাজের প্রোফাইল চালু করবেন?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"আপনার কাজের অ্যাপ, বিজ্ঞপ্তি, ডেটা এবং কাজের প্রোফাইলের অন্যান্য বৈশিষ্ট্য চালু করা হবে"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"চালু করুন"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"অ্যাপ পাওয়া যাচ্ছে না"</string> + <string name="app_blocked_message" msgid="542972921087873023">"এই মুহূর্তে <xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপ পাওয়া যাচ্ছে না।"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"এই অ্যাপটি Android এর একটি পুরনো ভার্সনের জন্য তৈরি করা হয়েছিল, তাই এখানে সেটি ঠিকমতো কাজ নাও করতে পারে। আপডেট পাওয়া যাচ্ছে কিনা দেখুন বা ডেভেলপারের সাথে যোগাযোগ করুন।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"আপডেট পাওয়া যাচ্ছে কিনা দেখুন"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"আপনার নতুন মেসেজ আছে"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index eaa212dccf2f..d436c5fbc87d 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -1887,6 +1887,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Uključiti radni profil?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Uključit će se poslovne aplikacije, obavještenja, podaci i druge funkcije radnog profila"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno nije dostupna."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova aplikacija je pravljena za stariju verziju Androida i možda neće ispravno raditi. Provjerite jesu li dostupna ažuriranja ili kontaktirajte programera."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri je li dostupno ažuriranje"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 0b58caea91b8..f3b416cee191 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -182,11 +182,11 @@ <item quantity="one">Autoritat de certificació instal·lada</item> </plurals> <string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Per un tercer desconegut"</string> - <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Per l\'administrador del teu perfil professional"</string> + <string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Per l\'administrador del teu perfil de treball"</string> <string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"Per <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string> - <string name="work_profile_deleted" msgid="5891181538182009328">"S\'ha suprimit el perfil professional"</string> - <string name="work_profile_deleted_details" msgid="3773706828364418016">"Falta l\'aplicació d\'administració del perfil professional o està malmesa. Com a conseqüència, s\'han suprimit el teu perfil professional i les dades relacionades. Contacta amb l\'administrador per obtenir ajuda."</string> - <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"El teu perfil professional ja no està disponible en aquest dispositiu"</string> + <string name="work_profile_deleted" msgid="5891181538182009328">"S\'ha suprimit el perfil de treball"</string> + <string name="work_profile_deleted_details" msgid="3773706828364418016">"Falta l\'aplicació d\'administració del perfil de treball o està malmesa. Com a conseqüència, s\'han suprimit el teu perfil de treball i les dades relacionades. Contacta amb l\'administrador per obtenir ajuda."</string> + <string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"El teu perfil de treball ja no està disponible en aquest dispositiu"</string> <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Has intentat introduir la contrasenya massa vegades"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"L\'administrador ha cedit el dispositiu per a ús personal"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"El dispositiu està gestionat"</string> @@ -280,7 +280,7 @@ <string name="safeMode" msgid="8974401416068943888">"Mode segur"</string> <string name="android_system_label" msgid="5974767339591067210">"Sistema Android"</string> <string name="user_owner_label" msgid="8628726904184471211">"Canvia al perfil personal"</string> - <string name="managed_profile_label" msgid="7316778766973512382">"Canvia al perfil professional"</string> + <string name="managed_profile_label" msgid="7316778766973512382">"Canvia al perfil de treball"</string> <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contactes"</string> <string name="permgroupdesc_contacts" msgid="9163927941244182567">"accedir als contactes"</string> <string name="permgrouplab_location" msgid="1858277002233964394">"Ubicació"</string> @@ -1406,8 +1406,8 @@ <string name="deny" msgid="6632259981847676572">"Denega"</string> <string name="permission_request_notification_title" msgid="1810025922441048273">"Permís sol·licitat"</string> <string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"S\'ha sol·licitat permís\nper al compte <xliff:g id="ACCOUNT">%s</xliff:g>."</string> - <string name="forward_intent_to_owner" msgid="4620359037192871015">"Estàs utilitzant aquesta aplicació fora del perfil professional."</string> - <string name="forward_intent_to_work" msgid="3620262405636021151">"Estàs utilitzant l\'aplicació al perfil professional."</string> + <string name="forward_intent_to_owner" msgid="4620359037192871015">"Estàs utilitzant aquesta aplicació fora del perfil de treball."</string> + <string name="forward_intent_to_work" msgid="3620262405636021151">"Estàs utilitzant l\'aplicació al perfil de treball."</string> <string name="input_method_binding_label" msgid="1166731601721983656">"Mètode d\'introducció de text"</string> <string name="sync_binding_label" msgid="469249309424662147">"Sincronització"</string> <string name="accessibility_binding_label" msgid="1974602776545801715">"Accessibilitat"</string> @@ -1817,7 +1817,7 @@ <string name="stk_cc_ss_to_dial_video" msgid="1324194624384312664">"La sol·licitud SS s\'ha canviat per una videotrucada"</string> <string name="stk_cc_ss_to_ussd" msgid="8417905193112944760">"La sol·licitud SS s\'ha canviat per una sol·licitud USSD"</string> <string name="stk_cc_ss_to_ss" msgid="132040645206514450">"S\'ha canviat a una nova sol·licitud SS"</string> - <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Perfil professional"</string> + <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Perfil de treball"</string> <string name="notification_alerted_content_description" msgid="6139691253611265992">"S\'ha enviat una alerta"</string> <string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Desplega"</string> <string name="expand_button_content_description_expanded" msgid="7484217944948667489">"Replega"</string> @@ -1850,15 +1850,17 @@ <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> no està disponible en aquests moments. Aquesta opció es gestiona a <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string> <string name="app_suspended_more_details" msgid="211260942831587014">"Més informació"</string> <string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Reactiva l\'aplicació"</string> - <string name="work_mode_off_title" msgid="5503291976647976560">"Activar el perfil professional?"</string> - <string name="work_mode_off_message" msgid="8417484421098563803">"S\'activaran les teves aplicacions de treball, les notificacions, les dades i altres funcions del perfil professional"</string> + <string name="work_mode_off_title" msgid="5503291976647976560">"Activar el perfil de treball?"</string> + <string name="work_mode_off_message" msgid="8417484421098563803">"S\'activaran les teves aplicacions de treball, les notificacions, les dades i altres funcions del perfil de treball"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activa"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"L\'aplicació no està disponible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Ara mateix, <xliff:g id="APP_NAME">%1$s</xliff:g> no està disponible."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aquesta aplicació es va crear per a una versió antiga d\'Android i pot ser que no funcioni correctament. Prova de cercar actualitzacions o contacta amb el desenvolupador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Cerca actualitzacions"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tens missatges nous"</string> <string name="new_sms_notification_content" msgid="3197949934153460639">"Obre l\'aplicació d\'SMS per veure\'ls"</string> <string name="profile_encrypted_title" msgid="9001208667521266472">"Algunes funcions poden ser limitades"</string> - <string name="profile_encrypted_detail" msgid="5279730442756849055">"Perfil professional bloquejat"</string> + <string name="profile_encrypted_detail" msgid="5279730442756849055">"Perfil de treball bloquejat"</string> <string name="profile_encrypted_message" msgid="1128512616293157802">"Toca per desbloquejar el perfil"</string> <string name="usb_mtp_launch_notification_title" msgid="774319638256707227">"S\'ha connectat a <xliff:g id="PRODUCT_NAME">%1$s</xliff:g>"</string> <string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Toca per veure els fitxers"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 5874c2493041..754a02016f8b 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Zapnout pracovní profil?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Vaše pracovní aplikace, oznámení, data a ostatní funkce pracovního účtu budou zapnuty"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Zapnout"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikace není k dispozici"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> v tuto chvíli není k dispozici."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Tato aplikace byla vytvořena pro starší verzi systému Android a nemusí fungovat správně. Zkuste vyhledat aktualizace, případně kontaktujte vývojáře."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Zkontrolovat aktualizace"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Máte nové zprávy"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index eb7ad1b9e48f..733c7f0e76a1 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Skal arbejdsprofilen slås til?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Dine arbejdsapps, notifikationer, data og andre funktioner til din arbejdsprofil deaktiveres"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Slå til"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Appen er ikke tilgængelig"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ikke tilgængelig lige nu."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Denne app er lavet til en ældre version af Android og fungerer muligvis ikke korrekt. Prøv at søge efter opdateringer, eller kontakt udvikleren."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Søg efter opdatering"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nye beskeder"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 6b01af1f3b08..9a4c606f76b3 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Arbeitsprofil aktivieren?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Deine geschäftlichen Apps, Benachrichtigungen, Daten und andere Funktionen des Arbeitsprofils werden aktiviert"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivieren"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App ist nicht verfügbar"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ist derzeit nicht verfügbar."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Diese App wurde für eine ältere Android-Version entwickelt und funktioniert möglicherweise nicht mehr richtig. Prüfe, ob Updates verfügbar sind oder kontaktiere den Entwickler."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Auf Updates prüfen"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Du hast neue Nachrichten"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 61ca692aae16..30a8d61d7b22 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ενεργοποίηση προφίλ εργασίας;"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Οι εφαρμογές, οι ειδοποιήσεις και τα δεδομένα εργασίας σας, καθώς και άλλες λειτουργίες του προφίλ εργασίας, θα ενεργοποιηθούν"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ενεργοποίηση"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Η εφαρμογή δεν είναι διαθέσιμη"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> δεν είναι διαθέσιμη αυτήν τη στιγμή."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Αυτή η εφαρμογή δημιουργήθηκε για παλαιότερη έκδοση του Android και μπορεί να μην λειτουργεί σωστά. Δοκιμάστε να ελέγξετε εάν υπάρχουν ενημερώσεις ή επικοινωνήστε με τον προγραμματιστή."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Έλεγχος για ενημέρωση"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Έχετε νέα μηνύματα"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 15a059f449f1..d15fc5a3c61e 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index f3ab25afdf1e..bec2e349ec67 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 15a059f449f1..d15fc5a3c61e 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 15a059f449f1..d15fc5a3c61e 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data and other work profile features will be turned on"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 871e528c8e94..42ec9321019e 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Turn on work profile?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Your work apps, notifications, data, and other work profile features will be turned on"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Turn on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is not available"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is not available right now."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 1cb9fde2956a..ac555d58a5e8 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"¿Activar el perfil de trabajo?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Se activaran las apps de trabajo, los datos, las notificaciones y otras funciones del perfil de trabajo"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"La app no está disponible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible en este momento."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta app se creó para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o comunícate con el programador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualización"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index d8509a331453..190664b4fd54 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -802,7 +802,7 @@ <string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Introduce el código PIN para desbloquear."</string> <string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"Código PIN incorrecto"</string> <string name="keyguard_label_text" msgid="3841953694564168384">"Para desbloquear el teléfono, pulsa la tecla de menú y, a continuación, pulsa 0."</string> - <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Número de emergencia"</string> + <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Llamada de emergencia"</string> <string name="lockscreen_carrier_default" msgid="6192313772955399160">"Sin servicio"</string> <string name="lockscreen_screen_locked" msgid="7364905540516041817">"Pantalla bloqueada"</string> <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pulsa la tecla de menú para desbloquear el teléfono o realizar una llamada de emergencia."</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"¿Activar el perfil de trabajo?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Tus aplicaciones, notificaciones, datos y otras funciones del perfil de trabajo se activarán"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"La aplicación no está disponible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"En estos momentos, <xliff:g id="APP_NAME">%1$s</xliff:g> no está disponible."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación se ha diseñado para una versión anterior de Android y es posible que no funcione correctamente. Busca actualizaciones o ponte en contacto con el desarrollador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualizaciones"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tienes mensajes nuevos"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 2148c09f08ad..0c302ee6b5fa 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Kas lülitada tööprofiil sisse?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Teie töörakendused, märguanded, andmed ja muud tööprofiili funktsioonid lülitatakse sisse"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Lülita sisse"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Rakendus ei ole saadaval"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole praegu saadaval."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"See rakendus on loodud Androidi vanema versiooni jaoks ega pruugi õigesti töötada. Otsige värskendusi või võtke ühendust arendajaga."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Otsi värskendust"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Teile on uusi sõnumeid"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 07bde5ee0648..e7aedd557225 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -517,7 +517,7 @@ <string name="permdesc_imagesWrite" msgid="5195054463269193317">"Argazki-bilduma aldatzeko baimena ematen die aplikazioei."</string> <string name="permlab_mediaLocation" msgid="7368098373378598066">"multimedia-edukien bildumako kokapena irakurri"</string> <string name="permdesc_mediaLocation" msgid="597912899423578138">"Multimedia-edukien bildumako kokapena irakurtzeko baimena ematen die aplikazioei."</string> - <string name="biometric_dialog_default_title" msgid="5284880398508155088">"Egiaztatu zu zarela"</string> + <string name="biometric_dialog_default_title" msgid="5284880398508155088">"Egiaztatu zeu zarela"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrikoa ez dago erabilgarri"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Utzi da autentifikazioa"</string> <string name="biometric_not_recognized" msgid="5106687642694635888">"Ez da ezagutu"</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Laneko profila aktibatu?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Laneko aplikazioak, jakinarazpenak, datuak eta laneko profileko bestelako eginbideak aktibatuko dira"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktibatu"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikazioa ez dago erabilgarri"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ez dago erabilgarri une honetan."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aplikazioa Android-en bertsio zaharrago baterako sortu zenez, baliteke behar bezala ez funtzionatzea. Bilatu eguneratzerik baden, edo jarri garatzailearekin harremanetan."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Bilatu eguneratzeak"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Mezu berriak dituzu"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index d65357b73ba0..9d9a13c5d75c 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"نمایه کاری روشن شود؟"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"برنامهها، اعلانها، دادهها و سایر قابلیتهای نمایه کاری شما روشن خواهد شد"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"روشن کردن"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"برنامه در دسترس نیست"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحالحاضر در دسترس نیست."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"این برنامه برای نسخه قدیمیتری از Android ساخته شده است و ممکن است درست کار نکند. وجود بهروزرسانی را بررسی کنید یا با برنامهنویس تماس بگیرید."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"بررسی وجود بهروزرسانی"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"پیامهای جدیدی دارید"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 17b3d33b4cf8..35cc8b9a8cb8 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Otetaanko työprofiili käyttöön?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Työsovellukset, ‑ilmoitukset, ‑tiedot ja muut työprofiiliominaisuudet otetaan käyttöön"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ota käyttöön"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Sovellus ei ole käytettävissä"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole nyt käytettävissä."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Tämä sovellus on suunniteltu vanhemmalle Android-versiolle eikä välttämättä toimi oikein. Kokeile tarkistaa päivitykset tai ottaa yhteyttä kehittäjään."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tarkista päivitykset"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Sinulle on uusia viestejä"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 45b8613a1e6f..caec9fa7615e 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Activer le profil professionnel?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Vos applications professionnelles, vos notifications, vos données et les autres fonctionnalités de profil professionnel seront activées"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activer"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"L\'application n\'est pas accessible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas accessible pour le moment."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Cette application a été conçue pour une ancienne version d\'Android et pourrait ne pas fonctionner correctement. Essayez de vérifier les mises à jour ou communiquez avec son concepteur."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Vérifier la présence de mises à jour"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Vous avez de nouveaux messages"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index c646aeb8d82d..601fe431a292 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Activer profil professionnel ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Vos applications professionnelles, notifications, données et d\'autres fonctionnalités de votre profil professionnel seront activées"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activer"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Application non disponible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> n\'est pas disponible pour le moment."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Cette application a été conçue pour une ancienne version d\'Android et risque de ne pas fonctionner correctement. Recherchez des mises à jour ou contactez le développeur."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Rechercher une mise à jour"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Vous avez de nouveaux messages"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 2a2d7c4712b9..0aa627716191 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Activar o perfil de traballo?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Activaranse as túas aplicacións de traballo, as notificacións, os datos e outras funcións do perfil de traballo"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"A aplicación non está dispoñible"</string> + <string name="app_blocked_message" msgid="542972921087873023">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> non está dispoñible neste momento."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación deseñouse para unha versión anterior de Android e quizais non funcione correctamente. Proba a buscar actualizacións ou contacta co programador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Buscar actualización"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tes mensaxes novas"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index aab09f751076..85bf30ed51f8 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"કાર્યાલયની પ્રોફાઇલ ચાલુ કરીએ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"તમારી કાર્યાલયની ઍપ, નોટિફિકેશન, ડેટા અને અન્ય કાર્યાલયની પ્રોફાઇલ સુવિધાઓ ચાલુ કરવામાં આવશે"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ચાલુ કરો"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ઍપ ઉપલબ્ધ નથી"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> હાલમાં ઉપલબ્ધ નથી."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"આ ઍપ Androidના જૂના વર્ઝન માટે બનાવવામાં આવ્યું હતું અને તે કદાચ તે યોગ્ય રીતે કાર્ય કરી શકશે નહીં. અપડેટ માટે તપાસવાનો પ્રયાસ કરો અથવા ડેવલપરનો સંપર્ક કરો."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"અપડેટ માટે તપાસો"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"તમારી પાસે નવા સંદેશા છે"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index e1bb060fbc76..19e154ef2046 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"वर्क प्रोफ़ाइल चालू करें?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"आपके काम से जुड़े ऐप्लिकेशन, सूचनाएं, डेटा और वर्क प्रोफ़ाइल से जुड़ी दूसरी सुविधाएं चालू हो जाएंगी"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"चालू करें"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ऐप्लिकेशन उपलब्ध नहीं है"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> इस समय उपलब्ध नहीं है."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"यह ऐप्लिकेशन Android के पुराने वर्शन के लिए बनाया गया था, इसलिए हो सकता है कि यह सही से काम न करे. देखें कि अपडेट मौजूद हैं या नहीं, या फिर डेवलपर से संपर्क करें."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"देखें कि अपडेट मौजूद है या नहीं"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"आपके पास नए संदेश हैं"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 383943429400..9a9357f188e2 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -1885,6 +1885,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Želite uključiti radni profil?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Uključit će se vaše radne aplikacije, obavijesti, podaci i druge značajke radnog profila"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Uključi"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija nije dostupna"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutačno nije dostupna."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ova je aplikacija razvijena za stariju verziju Androida i možda neće funkcionirati pravilno. Potražite ažuriranja ili se obratite razvojnom programeru."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Provjeri ažuriranja"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nove poruke"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index fcea1ec4975a..d4ac7e2b6a61 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Bekapcsolja a munkaprofilt?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"A munkahelyi alkalmazások, értesítések, adatok és a munkaprofilhoz tartozó egyéb funkciók be lesznek kapcsolva"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Bekapcsolás"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Az alkalmazás nem hozzáférhető"</string> + <string name="app_blocked_message" msgid="542972921087873023">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> jelenleg nem hozzáférhető."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ez az alkalmazás az Android egyik korábbi verziójához készült, így elképzelhető, hogy nem működik majd megfelelően ezen a rendszeren. Keressen frissítéseket, vagy vegye fel a kapcsolatot a fejlesztővel."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Frissítés keresése"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Új üzenetei érkeztek"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index cbd754f15e29..5cd8e8eeb8e5 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Միացնե՞լ աշխատանքային պրոֆիլը"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Ձեր աշխատանքային հավելվածները, ծանուցումները, տվյալները և աշխատանքային պրոֆիլի մյուս գործառույթները կմիանան"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Միացնել"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Հավելվածը հասանելի չէ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածն այս պահին հասանելի չէ։"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Այս հավելվածը ստեղծվել է Android-ի ավելի հին տարբերակի համար և կարող է պատշաճ չաշխատել: Ստուգեք թարմացումների առկայությունը կամ դիմեք մշակողին:"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Ստուգել նոր տարբերակի առկայությունը"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Դուք ունեք նոր հաղորդագրություններ"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index f1a94ff0149e..3a1571df64bd 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Aktifkan profil kerja?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikasi kerja, notifikasi, data, dan fitur profil kerja lainnya akan diaktifkan"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktifkan"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikasi tidak tersedia"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia saat ini."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Aplikasi ini dibuat untuk Android versi lama dan mungkin tidak berfungsi sebagaimana mestinya. Coba periksa apakah ada update, atau hubungi developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Periksa apakah ada update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Ada pesan baru"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index b4cb76d367b5..eddb21455101 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Kveikja á vinnusniði?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Kveikt verður á vinnuforritum, tilkynningum, gögnum og öðrum eiginleikum vinnusniðsins"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Kveikja"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Forrit er ekki tiltækt"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ekki tiltækt núna."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Þetta forrit var hannað fyrir eldri útgáfu af Android og ekki er víst að það virki eðlilega. Athugaðu hvort uppfærslur séu í boði eða hafðu samband við þróunaraðilann."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Leita að uppfærslu"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Þú ert með ný skilaboð"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 23a62a8a48f8..135a226e0cf5 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Attivare il profilo di lavoro?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Le tue app di lavoro, le notifiche, i dati e altri elementi del profilo di lavoro saranno attivati."</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Attiva"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"L\'app non è disponibile"</string> + <string name="app_blocked_message" msgid="542972921087873023">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> non è al momento disponibile."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Questa app è stata realizzata per una versione precedente di Android e potrebbe non funzionare correttamente. Prova a verificare la disponibilità di aggiornamenti o contatta lo sviluppatore."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Verifica la presenza di aggiornamenti"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Hai nuovi messaggi"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 4319aa8644f0..65b738461e56 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"להפעיל את פרופיל העבודה?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"אפליקציות העבודה, התראות, נתונים ותכונות נוספות של פרופיל העבודה יופעלו"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"הפעל"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"האפליקציה לא זמינה"</string> + <string name="app_blocked_message" msgid="542972921087873023">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> לא זמינה בשלב זה."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"האפליקציה הזו עוצבה לגרסה ישנה יותר של Android וייתכן שלא תפעל כראוי. ניתן לבדוק אם יש עדכונים או ליצור קשר עם המפתח."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"האם יש עדכון חדש?"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"יש לך הודעות חדשות"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 481af76de9a7..bda206442c6e 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"仕事用プロファイルの有効化"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"仕事用のアプリ、通知、データなど、仕事用プロファイルの機能が ON になります"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ON にする"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"アプリの利用不可"</string> + <string name="app_blocked_message" msgid="542972921087873023">"現在 <xliff:g id="APP_NAME">%1$s</xliff:g> はご利用になれません。"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"このアプリは以前のバージョンの Android 用に作成されており、正常に動作しない可能性があります。アップデートを確認するか、デベロッパーにお問い合わせください。"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"アップデートを確認"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"新着メッセージがあります"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index c2c28e515593..c47b71048163 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ჩაირთოს სამსახურის პროფილი?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"თქვენი სამსახურის აპები, შეტყობინებები, მონაცემები და სამსახურის პროფილის ყველა სხვა ფუნქცია ჩაირთვება"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ჩართვა"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"აპი მიუწვდომელია"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ამჟამად მიუწვდომელია."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ეს აპი Android-ის ძველი ვერსიისთვის შეიქმნა და შესაძლოა სათანადოდ არ მუშაობდეს. გადაამოწმეთ განახლებები ან დაუკავშირდით დეველოპერს."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"განახლების შემოწმება"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"თქვენ ახალი შეტყობინებები გაქვთ"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 7cb05129495d..241cf08bf594 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Жұмыс профилі қосылсын ба?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Жұмыс қолданбалары, хабарландырулар, деректер және басқа да жұмыс профильдерінің мүмкіндіктері қосылады"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Қосу"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Қолданба қолжетімді емес"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> қазір қолжетімді емес."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Қолданба Android жүйесінің ескі нұсқасына арналған және дұрыс жұмыс істемеуі мүмкін. Жаңартылған нұсқаны тексеріңіз немесе әзірлеушіге хабарласыңыз."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Жаңартылған нұсқаны тексеру"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Сізде жаңа хабарлар бар"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index e0267d363721..483e236c13c7 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -1855,6 +1855,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"បើកកម្រងព័ត៌មានការងារ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"កម្មវិធីការងារ ការជូនដំណឹង ទិន្នន័យ និងមុខងារកម្រងព័ត៌មានការងារផ្សេងទៀតរបស់អ្នកនឹងត្រូវបានបើក"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"បើក"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"មិនអាចប្រើកម្មវិធីនេះបានទេ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"មិនអាចប្រើ <xliff:g id="APP_NAME">%1$s</xliff:g> នៅពេលនេះបានទេ។"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"កម្មវិធីនេះត្រូវបានបង្កើតឡើងសម្រាប់កំណែប្រព័ន្ធប្រតិបត្តិការ Android ចាស់ ហើយវាអាចដំណើរការខុសប្រក្រតី។ សូមសាកល្បងពិនិត្យមើលកំណែថ្មី ឬទាក់ទងទៅអ្នកអភិវឌ្ឍន៍។"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"រកមើលកំណែថ្មី"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"អ្នកមានសារថ្មី"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 4a2bc72d27d8..7133e89a8436 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ ಆನ್ ಮಾಡುವುದೇ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ನಿಮ್ಮ ಕೆಲಸದ ಅಪ್ಲಿಕೇಶನ್ಗಳು, ಅಧಿಸೂಚನೆಗಳು, ಡೇಟಾ ಮತ್ತು ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ನ ಇತರ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಆನ್ ಮಾಡಲಾಗುತ್ತದೆ"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ಆನ್ ಮಾಡಿ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ಆ್ಯಪ್ ಲಭ್ಯವಿಲ್ಲ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಇದೀಗ ಲಭ್ಯವಿಲ್ಲ."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು Android ನ ಹಳೆಯ ಆವೃತ್ತಿಗೆ ರಚಿಸಲಾಗಿದೆ ಮತ್ತು ಸರಿಯಾಗಿ ಕೆಲಸ ಮಾಡದಿರಬಹುದು. ಅಪ್ಡೇಟ್ಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಪ್ರಯತ್ನಿಸಿ ಅಥವಾ ಡೆವಲಪರ್ ಅನ್ನು ಸಂಪರ್ಕಿಸಿ."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ಅಪ್ಡೇಟ್ಗಾಗಿ ಪರಿಶೀಲಿಸಿ"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"ನೀವು ಹೊಸ ಸಂದೇಶಗಳನ್ನು ಹೊಂದಿರುವಿರಿ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 0ac7a1447f7c..12c05baedbd7 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"직장 프로필을 사용 설정하시겠어요?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"업무용 앱, 알림, 데이터 및 기타 직장 프로필 기능이 사용 설정됩니다."</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"사용 설정"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"앱을 사용할 수 없습니다"</string> + <string name="app_blocked_message" msgid="542972921087873023">"현재 <xliff:g id="APP_NAME">%1$s</xliff:g> 앱을 사용할 수 없습니다."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"이 앱은 Android 이전 버전에 맞게 개발되었기 때문에 제대로 작동하지 않을 수 있습니다. 업데이트를 확인하거나 개발자에게 문의하세요."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"업데이트 확인"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"새 메시지 있음"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 88e36ff0b533..ccbf845ea1e0 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -190,7 +190,7 @@ <string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"Өтө көп жолу сырсөздү киргизүү аракети жасалды"</string> <string name="device_ownership_relinquished" msgid="4080886992183195724">"Админ түзмөктөн жеке колдонуу үчүн баш тартты"</string> <string name="network_logging_notification_title" msgid="554983187553845004">"Түзмөктү ишкана башкарат"</string> - <string name="network_logging_notification_text" msgid="1327373071132562512">"Ишканаңыз бул түзмөктү башкарат жана тармак трафигин көзөмөлдөшү мүмкүн. Чоо-жайын көрүү үчүн таптап коюңуз."</string> + <string name="network_logging_notification_text" msgid="1327373071132562512">"Ишканаңыз бул түзмөктү башкарат жана тармак трафигин көзөмөлдөшү мүмкүн. Чоо-жайын билгиңиз келсе, таптап коюңуз."</string> <string name="factory_reset_warning" msgid="6858705527798047809">"Түзмөгүңүз тазаланат"</string> <string name="factory_reset_message" msgid="2657049595153992213">"Түзмөктү башкаруучу колдонмо жараксыз. Түзмөгүңүз азыр тазаланат.\n\nСуроолоруңуз болсо, ишканаңыздын администраторуна кайрылыңыз."</string> <string name="printing_disabled_by" msgid="3517499806528864633">"Басып чыгаруу <xliff:g id="OWNER_APP">%s</xliff:g> тарабынан өчүрүлдү."</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Жумуш профили күйгүзүлсүнбү?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Жумуш колдонмолоруңуз, эскертмелериңиз, дайын-даректериңиз жана жумуш профилинин башка функциялары күйгүзүлөт."</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Күйгүзүү"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Колдонмо учурда жеткиликсиз"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> учурда жеткиликсиз"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Бул колдонмо Android\'дин эски версиясы үчүн иштеп чыгарылган, андыктан туура эмес иштеши мүмкүн. Жаңыртууларды издеп көрүңүз же иштеп чыгуучуга кайрылыңыз."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Жаңыртууну издөө"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Сизге жаңы билдирүүлөр келди"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index 63b87ac54e3e..e8bda532f31b 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ເປີດໃຊ້ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກບໍ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ແອັບວຽກຂອງທ່ານ, ການແຈ້ງເຕືອນ, ຂໍ້ມູນ ແລະ ຄຸນສົມບັດໂປຣໄຟລ໌ວຽກຈະຖືກເປີດໃຊ້"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ເປີດ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ແອັບບໍ່ສາມາດໃຊ້ໄດ້"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ່ສາມາດໃຊ້ໄດ້ໃນຕອນນີ້."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ແອັບນີ້ຖືກສ້າງຂຶ້ນສຳລັບ Android ເວີຊັນທີ່ເກົ່າກວ່າ ແລະ ອາດເຮັດວຽກໄດ້ບໍ່ປົກກະຕິ. ໃຫ້ລອງກວດສອບເບິ່ງອັບເດດ ຫຼື ຕິດຕໍ່ຜູ້ພັດທະນາ."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ກວດເບິ່ງອັບເດດ"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"ທ່ານມີຂໍ້ຄວາມໃໝ່"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index b8d848e91929..ad90ab3412e2 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Įjungti darbo profilį?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Darbo programos, pranešimai, duomenys ir kitos darbo profilio funkcijos bus išjungtos"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Įjungti"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Programa nepasiekiama."</string> + <string name="app_blocked_message" msgid="542972921087873023">"Programa „<xliff:g id="APP_NAME">%1$s</xliff:g>“ šiuo metu nepasiekiama."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ši programa sukurta naudoti senesnės versijos sistemoje „Android“ ir gali tinkamai neveikti. Pabandykite patikrinti, ar yra naujinių, arba susisiekite su kūrėju."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tikrinti, ar yra naujinių"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Turite naujų pranešimų"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index f168bb4121c8..6ffe39af644f 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -1885,6 +1885,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Vai ieslēgt darba profilu?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Tiks ieslēgtas jūsu darba lietotnes, paziņojumi, dati un citas darba profila funkcijas."</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ieslēgt"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Lietotne nav pieejama"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> pašlaik nav pieejama."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Šī lietotne tika izstrādāta vecākai Android versijai un var nedarboties pareizi. Meklējiet atjauninājumus vai sazinieties ar izstrādātāju."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Meklēt atjauninājumu"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Jums ir jaunas īsziņas."</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index f94d191fba28..bb1d9c6ca23e 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -1855,6 +1855,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Да се вклучи работниот профил?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Вашите работни апликации, известувања, податоци и други функции на работниот профил ќе бидат вклучени"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Вклучи"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Апликацијата не е достапна"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> не е достапна во моментов."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Апликацијава е создадена за постара верзија на Android и може да не функционира правилно. Проверете за ажурирања или контактирајте со програмерот."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверка за ажурирање"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нови пораки"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 1efd21132ebc..31edd8cc324d 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ഔദ്യോഗിക പ്രൊഫൈൽ ഓണാക്കണോ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"നിങ്ങളുടെ ഔദ്യോഗിക ആപ്പുകൾ, അറിയിപ്പുകൾ, ഡാറ്റ, മറ്റ് ഔദ്യോഗിക പ്രൊഫൈൽ ഫീച്ചറുകൾ എന്നിവ ഓണാക്കും"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ഓണാക്കുക"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ആപ്പ് ലഭ്യമല്ല"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ഇപ്പോൾ ലഭ്യമല്ല."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ഈ ആപ്പ് Android-ന്റെ പഴയ പതിപ്പിനായി നിർമ്മിച്ചിരിക്കുന്നതിനാൽ ശരിയായി പ്രവർത്തിച്ചേക്കില്ല. അപ്ഡേറ്റിനായി പരിശോധിക്കുക, അല്ലെങ്കിൽ ഡെവലപ്പറുമായി ബന്ധപ്പെടുക."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"അപ്ഡേറ്റിനായി പരിശോധിക്കുക"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"നിങ്ങൾക്ക് പുതിയ സന്ദേശങ്ങൾ ഉണ്ട്"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index cc031754b2b7..eb61a46d876a 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ажлын профайлыг асаах уу?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Таны ажлын апп, мэдэгдэл, өгөгдөл болон бусад ажлын профайлын онцлогийг асаана"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Асаах"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Апп боломжгүй байна"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> яг одоо боломжгүй байна."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Энэ аппыг Андройдын хуучин хувилбарт зориулсан бөгөөд буруу ажиллаж болзошгүй. Шинэчлэлтийг шалгаж эсвэл хөгжүүлэгчтэй холбогдоно уу."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Шинэчлэлтийг шалгах"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Танд шинэ зурвасууд байна"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index cd9b6fc5ee5a..0a8f4afa79ac 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"कार्य प्रोफाइल चालू ठेवायची?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"तुमची कार्य अॅप्स, सूचना, डेटा आणि अन्य कार्य प्रोफाइल वैशिष्ट्ये चालू केली जातील"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"चालू करा"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ॲप उपलब्ध नाही"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> आता उपलब्ध नाही."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"हे अॅप Android च्या जुन्या आवृत्ती साठी तयार करण्यात आले होते आणि योग्यरितीने कार्य करू शकणार नाही. अपडेट आहेत का ते तपासून पाहा, किंवा डेव्हलपरशी संपर्क साधण्याचा प्रयत्न करा."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"अपडेट आहे का ते तपासा"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"आपल्याकडे नवीन मेसेज आहेत"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 0a4073519b59..59c7d65afb32 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Hidupkan profil kerja?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Apl kerja, pemberitahuan, data dan ciri profil kerja anda yang lain akan dihidupkan"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Hidupkan"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Apl tidak tersedia"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> tidak tersedia sekarang."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Apl ini dibina untuk versi Android yang lebih lama dan mungkin tidak berfungsi dengan betul. Cuba semak kemas kini atau hubungi pembangun."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Semak kemas kini"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Anda mempunyai mesej baharu"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 9e048411b5c7..b3efcb180d90 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"အလုပ်ပရိုဖိုင် ဖွင့်လိုသလား။"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"သင်၏ အလုပ်အက်ပ်၊ အကြောင်းကြားချက်၊ ဒေတာနှင့် အခြားအလုပ်ပရိုဖိုင် ဝန်ဆောင်မှုများကို ဖွင့်လိုက်ပါမည်"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ဖွင့်ပါ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"အက်ပ်ကို မရနိုင်ပါ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ကို ယခု မရနိုင်ပါ။"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ဤအက်ပ်ကို Android ဗားရှင်းဟောင်းအတွက် ပြုလုပ်ထားခြင်းဖြစ်ပြီး ပုံမှန်အလုပ်မလုပ်နိုင်ပါ။ အပ်ဒိတ်များအတွက် ရှာကြည့်ပါ သို့မဟုတ် ဆော့ဖ်ဝဲအင်ဂျင်နီယာကို ဆက်သွယ်ပါ။"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"အပ်ဒိတ်စစ်ရန်"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"သင့်ထံတွင် စာအသစ်များရောက်နေသည်"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 45ab3e552c17..5a3a5886eb24 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Vil du slå på jobbprofilen?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Jobbappene dine samt varsler, data og andre funksjoner i jobbprofilen din blir slått på"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Slå på"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Appen er ikke tilgjengelig"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> er ikke tilgjengelig for øyeblikket."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Denne appen er utviklet for en eldre versjon av Android og fungerer kanskje ikke som den skal. Prøv å se etter oppdateringer, eller kontakt utvikleren."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Se etter oppdateringer"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nye meldinger"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 11033a740aa1..e95866ba1a7c 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -1859,6 +1859,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"कार्य प्रोफाइल सक्रिय गर्ने?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"तपाईंका कार्यसम्बन्धी अनुप्रयोग, सूचना, डेटा र कार्य प्रोफाइलका अन्य सुविधाहरू सक्रिय गरिने छन्"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"सक्रिय गर्नुहोस्"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"अनुप्रयोग उपलब्ध छैन"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> अहिले उपलब्ध छैन।"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"यो अनुप्रयोग Android को पुरानो संस्करणका लागि बनाइएको हुनाले यसले सही ढङ्गले काम नगर्न सक्छ। अद्यावधिकहरू उपलब्ध छन् वा छैनन् भनी जाँच गरी हेर्नुहोस् वा यसको विकासकर्तालाई सम्पर्क गर्नुहोस्।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"अद्यावधिक उपलब्ध छ वा छैन भनी जाँच गर्नुहोस्"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"तपाईंलाई नयाँ सन्देश आएको छ"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 13f6725c82b7..e524c1b71d85 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Werkprofiel inschakelen?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Je werk-apps, meldingen, gegevens en andere functies van je werkprofiel worden uitgeschakeld"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Inschakelen"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"App is niet beschikbaar"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> is momenteel niet beschikbaar."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Deze app is ontwikkeld voor een oudere versie van Android en werkt mogelijk niet op de juiste manier. Controleer op updates of neem contact op met de ontwikkelaar."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Controleren op update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Je hebt nieuwe berichten"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 4424af7dcab0..246b4d9b86f5 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ୱର୍କ ପ୍ରୋଫାଇଲ୍କୁ ଚାଲୁ କରିବେ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ଆପଣଙ୍କର କାର୍ଯ୍ୟକାରୀ ଆପ୍, ବିଜ୍ଞପ୍ତି, ଡାଟା ଓ ଅନ୍ୟ ୱର୍କ ପ୍ରୋଫାଇଲ୍ଗୁଡ଼ିକ ଚାଲୁ ହୋଇଯିବ"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ଅନ୍ କରନ୍ତୁ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବର୍ତ୍ତମାନ ଉପଲବ୍ଧ ନାହିଁ।"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ଏହି ଆପ୍କୁ Androidର ପୁରୁଣା ଭର୍ସନ୍ ପାଇଁ ନିର୍ମାଣ କରାଯାଇଥିଲା ଏବଂ ଠିକ୍ ଭାବେ କାମ କରିନପାରେ। ଏହାପାଇଁ ଅପଡେଟ୍ ଅଛି କି ନାହିଁ ଯାଞ୍ଚ କରନ୍ତୁ କିମ୍ବା ଡେଭେଲପର୍ଙ୍କ ସହିତ ସମ୍ପର୍କ କରନ୍ତୁ।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ଅପଡେଟ୍ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"ଆପଣଙ୍କ ପାଖରେ ନୂଆ ମେସେଜ୍ ରହିଛି"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 8c87581f9a61..3d688325fc6e 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"ਕੀ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਚਾਲੂ ਕਰਨੀ ਹੈ?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ਤੁਹਾਡੀਆਂ ਕਾਰਜ-ਸਥਾਨ ਐਪਾਂ, ਸੂਚਨਾਵਾਂ, ਡਾਟਾ ਅਤੇ ਹੋਰ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਚਾਲੂ ਕੀਤੀਆਂ ਜਾਣਗੀਆਂ"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ਚਾਲੂ ਕਰੋ"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ਐਪ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਐਪ ਇਸ ਵੇਲੇ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ਇਹ ਐਪ Android ਦੇ ਕਿਸੇ ਵਧੇਰੇ ਪੁਰਾਣੇ ਵਰਜਨ ਲਈ ਬਣਾਈ ਗਈ ਸੀ ਅਤੇ ਸ਼ਾਇਦ ਸਹੀ ਢੰਗ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ। ਅੱਪਡੇਟਾਂ ਲਈ ਜਾਂਚ ਕਰੋ ਜਾਂ ਵਿਕਾਸਕਾਰ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ਅੱਪਡੇਟ ਦੀ ਜਾਂਚ ਕਰੋ"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"ਤੁਹਾਨੂੰ ਨਵੇਂ ਸੁਨੇਹੇ ਪ੍ਰਾਪਤ ਹੋਏ ਹਨ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 5efbfbe36830..2fb9ac20ba23 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Włączyć profil służbowy?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikacje do pracy, powiadomienia, dane i inne funkcje profilu do pracy zostaną włączone"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Włącz"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacja jest niedostępna"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> jest obecnie niedostępna."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ta aplikacja jest na starszą wersję Androida i może nie działać prawidłowo. Sprawdź dostępność aktualizacji lub skontaktuj się z programistą."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Sprawdź dostępność aktualizacji"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Masz nowe wiadomości"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index db4844163ba9..1ae0c360bf11 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -509,8 +509,8 @@ <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Permite que o app execute métodos para adicionar e excluir modelos de impressão digital para uso."</string> <string name="permlab_useFingerprint" msgid="1001421069766751922">"usar hardware de impressão digital"</string> <string name="permdesc_useFingerprint" msgid="412463055059323742">"Permite que o app use hardware de impressão digital para autenticação."</string> - <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua coleção de músicas"</string> - <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua coleção de músicas."</string> + <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua biblioteca de música"</string> + <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua biblioteca de música."</string> <string name="permlab_videoWrite" msgid="5940738769586451318">"modificar sua coleção de vídeos"</string> <string name="permdesc_videoWrite" msgid="6124731210613317051">"Permite que o app modifique sua coleção de vídeos."</string> <string name="permlab_imagesWrite" msgid="1774555086984985578">"modificar sua coleção de fotos"</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Seus apps, notificações, dados e outros recursos do perfil de trabalho serão ativados"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string> + <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Este app foi criado para uma versão mais antiga do Android e pode não funcionar corretamente. Tente verificar se há atualizações ou entre em contato com o desenvolvedor."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Procurar atualizações"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Você tem mensagens novas"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 04296fe10378..c381709f61db 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"As aplicações de trabalho, as notificações, os dados e outras funcionalidades do perfil de trabalho serão desativados"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"A app não está disponível"</string> + <string name="app_blocked_message" msgid="542972921087873023">"De momento, a app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicação foi concebida para uma versão mais antiga do Android e pode não funcionar corretamente. Experimente verificar se existem atualizações ou contacte o programador."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Verificar se existem atualizações"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Tem mensagens novas"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index db4844163ba9..1ae0c360bf11 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -509,8 +509,8 @@ <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Permite que o app execute métodos para adicionar e excluir modelos de impressão digital para uso."</string> <string name="permlab_useFingerprint" msgid="1001421069766751922">"usar hardware de impressão digital"</string> <string name="permdesc_useFingerprint" msgid="412463055059323742">"Permite que o app use hardware de impressão digital para autenticação."</string> - <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua coleção de músicas"</string> - <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua coleção de músicas."</string> + <string name="permlab_audioWrite" msgid="8501705294265669405">"modificar sua biblioteca de música"</string> + <string name="permdesc_audioWrite" msgid="8057399517013412431">"Permite que o app modifique sua biblioteca de música."</string> <string name="permlab_videoWrite" msgid="5940738769586451318">"modificar sua coleção de vídeos"</string> <string name="permdesc_videoWrite" msgid="6124731210613317051">"Permite que o app modifique sua coleção de vídeos."</string> <string name="permlab_imagesWrite" msgid="1774555086984985578">"modificar sua coleção de fotos"</string> @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ativar o perfil de trabalho?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Seus apps, notificações, dados e outros recursos do perfil de trabalho serão ativados"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Ativar"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"O app não está disponível"</string> + <string name="app_blocked_message" msgid="542972921087873023">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> não está disponível no momento."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Este app foi criado para uma versão mais antiga do Android e pode não funcionar corretamente. Tente verificar se há atualizações ou entre em contato com o desenvolvedor."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Procurar atualizações"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Você tem mensagens novas"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 030e76a2f6cf..7c5b63efdb02 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1885,6 +1885,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Activați profilul de serviciu?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Se vor activa aplicațiile dvs. de serviciu, notificările, datele și alte funcții ale profilului de serviciu"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Activați"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplicația nu este disponibilă"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nu este disponibilă momentan."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Această aplicație a fost creată pentru o versiune Android mai veche și este posibil să nu funcționeze corect. Încercați să căutați actualizări sau contactați dezvoltatorul."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Căutați actualizări"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Aveți mesaje noi"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 94029f51c0c0..fa2c9f5697ab 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -1177,7 +1177,7 @@ <string name="whichEditApplication" msgid="6191568491456092812">"Редактировать с помощью приложения:"</string> <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Редактировать с помощью приложения \"%1$s\""</string> <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Изменить"</string> - <string name="whichSendApplication" msgid="4143847974460792029">"Отправка данных"</string> + <string name="whichSendApplication" msgid="4143847974460792029">"Поделиться"</string> <string name="whichSendApplicationNamed" msgid="4470386782693183461">"Поделиться через %1$s"</string> <string name="whichSendApplicationLabel" msgid="7467813004769188515">"Поделиться"</string> <string name="whichSendToApplication" msgid="77101541959464018">"Выберите приложение"</string> @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Включить рабочий профиль?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Будут включены корпоративные приложения, уведомления, данные и другие функции рабочего профиля."</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Включить"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Приложение недоступно"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" сейчас недоступно."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Это приложение было создано для более ранней версии Android и может работать со сбоями. Проверьте наличие обновлений или свяжитесь с разработчиком."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Проверить обновления"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Новые сообщения"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 73cf01890108..dbe1f2203c84 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -1855,6 +1855,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"කාර්යාල පැතිකඩ ක්රියාත්මක කරන්නද?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ඔබගේ වැඩ යෙදුම්, දැනුම්දීම්, දත්ත සහ වෙනත් කාර්යාල පැතිකඩ විශේෂාංග ක්රියාත්මක කරනු ඇත"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ක්රියාත්මක කරන්න"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"යෙදුම ලබා ගත නොහැකිය"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> මේ දැන් ලබා ගත නොහැකිය."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"මෙම යෙදුම Android හි පැරණි අනුවාදයක් සඳහා තනා ඇති අතර නිසියාකාරව ක්රියා නොකරනු ඇත. යාවත්කාලීන සඳහා පරික්ෂා කිරීම උත්සාහ කරන්න, නැතහොත් සංවර්ධක අමතන්න."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"යාවත්කාලීන සඳහා පරික්ෂා කරන්න"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"ඔබට නව පණිවිඩ තිබේ"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index b86fc4696a01..5483efdb4d51 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Zapnúť pracovný profil?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Pracovné aplikácie, upozornenia, dáta a ďalšie funkcie pracovného profilu sa zapnú"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Zapnúť"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikácia nie je dostupná"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> nie je teraz dostupná."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Táto aplikácia bola zostavená pre staršiu verziu Androidu a nemusí správne fungovať. Skúste skontrolovať dostupnosť aktualizácií alebo kontaktovať vývojára."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Skontrolovať dostupnosť aktualizácie"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Máte nové správy."</string> @@ -1944,7 +1946,7 @@ <string name="app_category_social" msgid="2278269325488344054">"Sociálne siete a komunikácia"</string> <string name="app_category_news" msgid="1172762719574964544">"Noviny a časopisy"</string> <string name="app_category_maps" msgid="6395725487922533156">"Mapy a navigácia"</string> - <string name="app_category_productivity" msgid="1844422703029557883">"Produktivita"</string> + <string name="app_category_productivity" msgid="1844422703029557883">"Kancelárske"</string> <string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Úložisko zariadenia"</string> <string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"Ladenie cez USB"</string> <string name="time_picker_hour_label" msgid="4208590187662336864">"hodina"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index bc2bc3acb7db..fed39e8343f4 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Želite vklopiti delovni profil?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Vklopili boste svoje delovne aplikacije, obvestila, podatke in druge funkcije delovnega profila"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Vklop"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacija ni na voljo"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> trenutno ni na voljo."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ta aplikacija je bila zasnovana za starejšo različico Androida in morda ne bo delovala pravilno. Preverite, ali so na voljo posodobitve, ali pa se obrnite na razvijalca."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Preveri, ali je na voljo posodobitev"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Imate nova sporočila."</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index a35328879f29..ec61fb966b5e 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Të aktivizohet profili i punës?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Aplikacionet e punës, njoftimet, të dhënat e tua dhe funksionet e tjera të profilit të punës do të aktivizohen"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivizo"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Aplikacioni nuk ofrohet"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> nuk ofrohet për momentin."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ky aplikacion është ndërtuar për një version më të vjetër të Android dhe mund të mos funksionojë mirë. Provo të kontrollosh për përditësime ose kontakto me zhvilluesin."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kontrollo për përditësim"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Ke mesazhe të reja"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 8ee644ad2e4f..e1c4e1822e5b 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -1885,6 +1885,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Да укључимо профил за Work?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Укључиће се пословне апликације, обавештења, подаци и друге функције профила за Work"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Укључи"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Апликација није доступна"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> тренутно није доступна."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ова апликација је направљена за старију верзију Android-а, па можда неће радити исправно. Потражите ажурирања или контактирајте програмера."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Потражи ажурирање"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Имате нове поруке"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 577933e6a296..0737b22c4aea 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Vill du aktivera jobbprofilen?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Jobbappar, aviseringar, data och andra funktioner i jobbprofilen aktiveras"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aktivera"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Appen är inte tillgänglig"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> är inte tillgängligt just nu."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Appen har utvecklats för en äldre version av Android och kanske inte fungerar som den ska. Testa att söka efter uppdateringar eller kontakta utvecklaren."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Sök efter uppdateringar"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Du har nya meddelanden"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index a6ee3667b3c1..3c6b0fad22c8 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ungependa kuwasha wasifu wa kazini?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Hatua hii itawasha data, arifa, programu za kazini, arifa na vipengele vingine vya wasifu wa kazini"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Washa"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Programu haipatikani"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> haipatikani hivi sasa."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Programu hii iliundwa kwa ajili ya toleo la zamani la Android na huenda isifanye kazi vizuri. Jaribu kuangalia masasisho au uwasiliane na msanidi programu."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Angalia masasisho"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Una ujumbe mpya"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index a56465fcd8ac..e80f37becd91 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"பணிச் சுயவிவரத்தை ஆன் செய்யவா?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"பணி ஆப்ஸ், அறிவிப்புகள், தரவு மற்றும் பிற பணிச் சுயவிவர அம்சங்கள் ஆன் செய்யப்படும்"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"இயக்கு"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"இந்த ஆப்ஸ் இப்போது கிடைப்பதில்லை"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் இப்போது கிடைப்பதில்லை."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"இந்த ஆப்ஸ் Android இன் பழைய பதிப்புக்காக உருவாக்கப்பட்டதால், சரியாக வேலை செய்யாமல் போகலாம். புதுப்பிப்புகள் ஏதேனும் உள்ளதா எனப் பார்க்கவும் அல்லது டெவெலப்பரைத் தொடர்புகொள்ளவும்."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"புதுப்பிப்பு உள்ளதா எனப் பார்"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"புதிய செய்திகள் வந்துள்ளன"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 7a0aece84500..81d4b4fd0220 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"కార్యాలయ ప్రొఫైల్ని ఆన్ చేయాలా?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"మీ కార్యాలయ యాప్లు, నోటిఫికేషన్లు, డేటా మరియు ఇతర కార్యాలయ ప్రొఫైల్ ఫీచర్లు ఆన్ చేయబడతాయి"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"ఆన్ చేయి"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"యాప్ అందుబాటులో లేదు"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ప్రస్తుతం అందుబాటులో లేదు."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"ఈ యాప్ పాత వెర్షన్ Android కోసం రూపొందించబడింది మరియు అది సరిగ్గా పని చేయకపోవచ్చు. అప్డేట్ల కోసం తనిఖీ చేయడానికి ప్రయత్నించండి లేదా డెవలపర్ని సంప్రదించండి."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"అప్డేట్ కోసం తనిఖీ చేయండి"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"మీకు కొత్త సందేశాలు ఉన్నాయి"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 98f2d8880b8a..46530ca33912 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"เปิดโปรไฟล์งานไหม"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"ระบบจะเปิดแอปงาน การแจ้งเตือน ข้อมูล และฟีเจอร์อื่นๆ ในโปรไฟล์งาน"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"เปิด"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"แอปไม่พร้อมใช้งาน"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ไม่พร้อมใช้งานในขณะนี้"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"แอปนี้สร้างขึ้นเพื่อ Android เวอร์ชันเก่าและอาจทำงานผิดปกติ โปรดลองตรวจหาการอัปเดตหรือติดต่อนักพัฒนาซอฟต์แวร์"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ตรวจสอบอัปเดต"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"คุณมีข้อความใหม่"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index c38a6d54cdbd..b9f1f293475b 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"I-on ang profile sa trabaho?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Mao-on ang iyong mga app sa trabaho, notification, data, at iba pang feature sa profile sa trabaho"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"I-on"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Hindi available ang app"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Hindi available sa ngayon ang <xliff:g id="APP_NAME">%1$s</xliff:g>."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ang app na ito ay ginawa para sa mas lumang bersyon ng Android at maaaring hindi gumana nang maayos. Subukang tingnan kung may mga update, o makipag-ugnayan sa developer."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Tingnan kung may update"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Mayroon kang mga bagong mensahe"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 5e0930947afb..81fb07933f0c 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"İş profili açılsın mı?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"İş uygulamalarınız, bildirimleriniz, verileriniz ve diğer iş profili özellikleriniz açılacak"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Aç"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Uygulama kullanılamıyor"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> uygulaması şu anda kullanılamıyor."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu uygulama Android\'in daha eski bir sürümü için oluşturuldu ve düzgün çalışmayabilir. Güncellemeleri kontrol etmeyi deneyin veya geliştiriciyle iletişime geçin."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Güncellemeleri denetle"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Yeni mesajlarınız var"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 1096f97e3c98..c71ea74ccaad 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -1917,6 +1917,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Увімкнути робочий профіль?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Додатки, сповіщення, дані й інші функції робочого профілю буде ввімкнено"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Увімкнути"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Додаток недоступний"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> зараз недоступний."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Цей додаток створений для старішої версії Android і може працювати неналежним чином. Спробуйте знайти оновлення або зв’яжіться з розробником."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Шукати оновлення"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"У вас є нові повідомлення"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 4c331916bb57..8a0425bdcd34 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"دفتری پروفائل آن کریں؟"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"آپ کی دفتری ایپس، اطلاعات، ڈیٹا اور دفتری پروفائل کی دیگر خصوصیات آن کر دی جائیں گی"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"آن کریں"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"ایپ دستیاب نہیں ہے"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ابھی دستیاب نہیں ہے۔"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"یہ ایپ Android کے پرانے ورژن کے لئے بنائی گئی ہے اور ہو سکتا ہے صحیح طور پر کام نہ کرے۔ اپ ڈیٹس چیک کر کے آزمائیں یا ڈیولپر سے رابطہ کریں۔"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"اپ ڈیٹ چیک کریں"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"آپ کے پاس نئے پیغامات ہیں"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 23873b5f2b4f..b5c31c2f50dd 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Ishchi profil yoqilsinmi?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Ishchi ilovalar, bildirishnomalar, ma’lumotlar va boshqa ishchi profil imkoniyatlari yoqiladi"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Yoqish"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Ilova ishlamayapti"</string> + <string name="app_blocked_message" msgid="542972921087873023">"Ayni vaqtda <xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi ishlamayapti."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Bu ilova eskiroq Android versiyalariga chiqarilgan va xato ishlashi mumkin. Yangilanishlarini tekshiring yoki dasturchi bilan bog‘laning."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Yangilanish borligini tekshirish"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Sizga yangi SMS keldi"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 1ed30d7db57e..970d41ff9203 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Bạn muốn bật hồ sơ công việc?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Ứng dụng công việc, thông báo, dữ liệu và các tính năng khác của hồ sơ công việc sẽ được bật"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Bật"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Ứng dụng này không dùng được"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> hiện không dùng được."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Ứng dụng này được xây dựng cho một phiên bản Android cũ hơn và có thể hoạt động không bình thường. Hãy thử kiểm tra các bản cập nhật hoặc liên hệ với nhà phát triển."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Kiểm tra bản cập nhật"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Bạn có tin nhắn mới"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 7671063821d0..c9bba5902788 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"要开启工作资料吗?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"您的工作应用、通知、数据及其他工作资料功能将会开启"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"开启"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"应用无法使用"</string> + <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g>目前无法使用。"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"此应用专为旧版 Android 打造,因此可能无法正常运行。请尝试检查更新或与开发者联系。"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"检查更新"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"您有新消息"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 89bea07ae1a7..fc652e3b59e5 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"要開啟工作設定檔嗎?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"系統將開啟您的工作應用程式、通知、資料和其他工作設定檔功能"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"開啟"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"無法使用應用程式"</string> + <string name="app_blocked_message" msgid="542972921087873023">"目前無法使用「<xliff:g id="APP_NAME">%1$s</xliff:g>」。"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"此應用程式專為舊版 Android 打造,因此可能無法正常運作。請嘗試檢查更新,或與開發人員聯絡。"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"您有新的訊息"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index c95ae37602a0..75d05a29f2c9 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"要開啟工作資料夾嗎?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"系統將開啟你的辦公應用程式、通知、資料和其他工作資料夾功能"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"開啟"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"應用程式無法使用"</string> + <string name="app_blocked_message" msgid="542972921087873023">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」目前無法使用。"</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"這個應用程式是專為舊版 Android 所打造,因此可能無法正常運作。請嘗試檢查更新,或是與開發人員聯絡。"</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"你有新訊息"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 0cd669bb07a7..0d2abc0c4aa7 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -1853,6 +1853,8 @@ <string name="work_mode_off_title" msgid="5503291976647976560">"Vula iphrofayela yomsebenzi?"</string> <string name="work_mode_off_message" msgid="8417484421098563803">"Izinhlelo zakho zokusebenza zomsebenzi, izaziso, idatha, nezinye izici zephrofayela yomsebenzi kuzovulwa"</string> <string name="work_mode_turn_on" msgid="3662561662475962285">"Vula"</string> + <string name="app_blocked_title" msgid="7353262160455028160">"Uhlelo lokusebenza alutholakali"</string> + <string name="app_blocked_message" msgid="542972921087873023">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> ayitholakali khona manje."</string> <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Lolu hlelo lokusebenza belakhelwe inguqulo endala ye-Android futhi kungenzeka lungasebenzi kahle. Zama ukuhlolela izibuyekezo, noma uxhumane nonjiniyela."</string> <string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Hlola izibuyekezo"</string> <string name="new_sms_notification_title" msgid="6528758221319927107">"Unemilayezo emisha"</string> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index b22e1867f257..c66261bb6630 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2056,6 +2056,9 @@ <attr name="name" /> </declare-styleable> <declare-styleable name="AndroidManifestQueriesIntent" parent="AndroidManifestQueries" /> + <declare-styleable name="AndroidManifestQueriesProvider" parent="AndroidManifestQueries" > + <attr name="authorities" /> + </declare-styleable> <!-- The <code>static-library</code> tag declares that this apk is providing itself diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 31e68e88c0a8..7f574ba2036b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3402,8 +3402,8 @@ <!-- True if assistant app should be pinned via Pinner Service --> <bool name="config_pinnerAssistantApp">false</bool> - <!-- List of files pinned by the Pinner Service with the apex boot image b/119800099 --> - <string-array translatable="false" name="config_apexBootImagePinnerServiceFiles"> + <!-- List of files pinned by the Pinner Service with the JIT Zygote boot image b/119800099 --> + <string-array translatable="false" name="config_jitzygoteBootImagePinnerServiceFiles"> </string-array> <!-- Number of days preloaded file cache should be preserved on a device before it can be @@ -3792,6 +3792,10 @@ or empty if the default should be used. --> <string translatable="false" name="config_deviceSpecificAudioService"></string> + <!-- Class name of the device specific implementation of DisplayAreaPolicy.Provider + or empty if the default should be used. --> + <string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider"></string> + <!-- Component name of media projection permission dialog --> <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a54566cfed17..08c840380fe5 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -414,6 +414,14 @@ logging. [CHAR LIMIT=NONE]--> <string name="network_logging_notification_text">Your organization manages this device and may monitor network traffic. Tap for details.</string> + <!-- Content title for a notification. This notification indicates that the device owner has + changed the location settings. [CHAR LIMIT=NONE] --> + <string name="location_changed_notification_title">Location settings changed by your admin</string> + <!-- Content text for a notification. Tapping opens device location settings. + [CHAR LIMIT=NONE] --> + <string name="location_changed_notification_text">Tap to see your location settings.</string> + + <!-- Factory reset warning dialog strings--> <skip /> <!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] --> <string name="factory_reset_warning">Your device will be erased</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2453bb18577f..0babe48e832e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1213,6 +1213,8 @@ <java-symbol type="string" name="device_ownership_relinquished" /> <java-symbol type="string" name="network_logging_notification_title" /> <java-symbol type="string" name="network_logging_notification_text" /> + <java-symbol type="string" name="location_changed_notification_title" /> + <java-symbol type="string" name="location_changed_notification_text" /> <java-symbol type="string" name="personal_apps_suspended_notification_title" /> <java-symbol type="string" name="personal_apps_suspended_notification_text" /> <java-symbol type="string" name="factory_reset_warning" /> @@ -3037,7 +3039,7 @@ <java-symbol type="bool" name="config_pinnerCameraApp" /> <java-symbol type="bool" name="config_pinnerHomeApp" /> <java-symbol type="bool" name="config_pinnerAssistantApp" /> - <java-symbol type="array" name="config_apexBootImagePinnerServiceFiles" /> + <java-symbol type="array" name="config_jitzygoteBootImagePinnerServiceFiles" /> <java-symbol type="string" name="config_doubleTouchGestureEnableFile" /> @@ -3864,6 +3866,8 @@ <!-- Toast message for background started foreground service while-in-use permission restriction feature --> <java-symbol type="string" name="allow_while_in_use_permission_in_fgs" /> + <java-symbol type="string" name="config_deviceSpecificDisplayAreaPolicyProvider" /> + <!-- Whether to expand the lock screen user switcher by default --> <java-symbol type="bool" name="config_expandLockScreenUserSwitcher" /> </resources> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 9f15fafe2bf1..e04d3de622d8 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -52,9 +52,14 @@ android_test { "android.test.base", "android.test.mock", "framework-atb-backward-compatibility", + "framework-all", + "icing-java-proto-lite", + "ext", + "framework-res", ], platform_apis: true, + sdk_version: "core_platform", test_suites: ["device-tests"], certificate: "platform", diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 59335a595334..718ca46a4f18 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1330,6 +1330,12 @@ android:process=":FakeProvider"> </provider> + <provider + android:name="android.content.SlowProvider" + android:authorities="android.content.SlowProvider" + android:process=":SlowProvider"> + </provider> + <!-- Application components used for os tests --> <service android:name="android.os.MessengerService" diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java index 0a21875fd77d..d1608d055604 100644 --- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java +++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java @@ -234,6 +234,40 @@ public class NotificationHistoryTest { } @Test + public void testRemoveNotificationFromWrite() { + NotificationHistory history = new NotificationHistory(); + + List<HistoricalNotification> postRemoveExpectedEntries = new ArrayList<>(); + List<String> postRemoveExpectedStrings = new ArrayList<>(); + for (int i = 1; i <= 10; i++) { + HistoricalNotification n = getHistoricalNotification("pkg", i); + + if (987654323 != n.getPostedTimeMs()) { + postRemoveExpectedStrings.add(n.getPackage()); + postRemoveExpectedStrings.add(n.getChannelName()); + postRemoveExpectedStrings.add(n.getChannelId()); + postRemoveExpectedEntries.add(n); + } + + history.addNotificationToWrite(n); + } + + history.poolStringsFromNotifications(); + + assertThat(history.getNotificationsToWrite().size()).isEqualTo(10); + // 1 package name and 10 unique channel names and ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(21); + + history.removeNotificationFromWrite("pkg", 987654323); + + + // 1 package names and 9 * 2 unique channel names and ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(19); + assertThat(history.getNotificationsToWrite()) + .containsExactlyElementsIn(postRemoveExpectedEntries); + } + + @Test public void testParceling() { NotificationHistory history = new NotificationHistory(); diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java index 9dcce1e51e0b..6dc7392945d8 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -209,4 +209,13 @@ public class ContentResolverTest { String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderRemote")); assertEquals("fake/remote", type); } + + + @Test + public void testGetType_slowProvider() { + // This provider is running in a different process and is intentionally slow to start. + // We are trying to confirm that it does not cause an ANR + String type = mResolver.getType(Uri.parse("content://android.content.SlowProvider")); + assertEquals("slow", type); + } } diff --git a/core/tests/coretests/src/android/content/SlowProvider.java b/core/tests/coretests/src/android/content/SlowProvider.java new file mode 100644 index 000000000000..aba32e836e80 --- /dev/null +++ b/core/tests/coretests/src/android/content/SlowProvider.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.database.Cursor; +import android.net.Uri; + +/** + * A dummy content provider for tests. This provider runs in a different process from the test and + * is intentionally slow. + */ +public class SlowProvider extends ContentProvider { + + private static final int ON_CREATE_LATENCY_MILLIS = 3000; + + @Override + public boolean onCreate() { + try { + Thread.sleep(ON_CREATE_LATENCY_MILLIS); + } catch (InterruptedException e) { + // Ignore + } + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return "slow"; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} diff --git a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java index 3273e5dd32ba..ea6917682965 100644 --- a/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/AtomicFormulaTest.java @@ -29,6 +29,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.Arrays; +import java.util.Collections; + @RunWith(JUnit4.class) public class AtomicFormulaTest { @@ -230,7 +233,7 @@ public class AtomicFormulaTest { } @Test - public void testFormulaMatches_string_true() { + public void testFormulaMatches_string_packageNameFormula_true() { StringAtomicFormula stringAtomicFormula = new StringAtomicFormula( AtomicFormula.PACKAGE_NAME, "com.test.app", /* isHashedValue= */ @@ -242,7 +245,7 @@ public class AtomicFormulaTest { } @Test - public void testFormulaMatches_string_false() { + public void testFormulaMatches_string_packageNameFormula_false() { StringAtomicFormula stringAtomicFormula = new StringAtomicFormula( AtomicFormula.PACKAGE_NAME, "com.test.app", /* isHashedValue= */ @@ -253,6 +256,63 @@ public class AtomicFormulaTest { assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse(); } + @Test + public void testFormulaMatches_string_multipleAppCertificates_true() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, "cert", /* isHashedValue= */ true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder() + .setPackageName("com.test.app") + .setAppCertificates(Arrays.asList("test-cert", "cert")) + .build(); + + assertThat(stringAtomicFormula.matches(appInstallMetadata)).isTrue(); + } + + @Test + public void testFormulaMatches_string_multipleAppCertificates_false() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.APP_CERTIFICATE, "cert", /* isHashedValue= */ true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder() + .setPackageName("com.test.app") + .setAppCertificates(Arrays.asList("test-cert", "another-cert")) + .build(); + + assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse(); + } + + @Test + public void testFormulaMatches_string_multipleInstallerCertificates_true() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.INSTALLER_CERTIFICATE, "cert", /* isHashedValue= */ true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder() + .setPackageName("com.test.app") + .setAppCertificates(Collections.singletonList("abc")) + .setInstallerCertificates(Arrays.asList("test-cert", "cert")) + .build(); + + assertThat(stringAtomicFormula.matches(appInstallMetadata)).isTrue(); + } + + @Test + public void testFormulaMatches_string_multipleInstallerCertificates_false() { + StringAtomicFormula stringAtomicFormula = + new StringAtomicFormula( + AtomicFormula.INSTALLER_CERTIFICATE, "cert", /* isHashedValue= */ true); + AppInstallMetadata appInstallMetadata = + getAppInstallMetadataBuilder() + .setPackageName("com.test.app") + .setAppCertificates(Collections.singletonList("abc")) + .setInstallerCertificates(Arrays.asList("test-cert", "another-cert")) + .build(); + + assertThat(stringAtomicFormula.matches(appInstallMetadata)).isFalse(); + } @Test public void testIsAppCertificateFormula_string_true() { @@ -430,8 +490,8 @@ public class AtomicFormulaTest { private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { return new AppInstallMetadata.Builder() .setPackageName("abc") - .setAppCertificate("abc") - .setInstallerCertificate("abc") + .setAppCertificates(Collections.singletonList("abc")) + .setInstallerCertificates(Collections.singletonList("abc")) .setInstallerName("abc") .setVersionCode(-1) .setIsPreInstalled(true); diff --git a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java index f47dfdd191fb..abc5fed181de 100644 --- a/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/CompoundFormulaTest.java @@ -286,8 +286,8 @@ public class CompoundFormulaTest { private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { return new AppInstallMetadata.Builder() .setPackageName("abc") - .setAppCertificate("abc") - .setInstallerCertificate("abc") + .setAppCertificates(Collections.singletonList("abc")) + .setInstallerCertificates(Collections.singletonList("abc")) .setInstallerName("abc") .setVersionCode(-1) .setIsPreInstalled(true); diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java index 2c956c990e97..669138c15698 100644 --- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java +++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java @@ -148,6 +148,15 @@ public class ConfigurationTest extends TestCase { assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED, config.smallestScreenWidthDp); } + @Test + public void testNightModeHelper() { + Configuration config = new Configuration(); + config.uiMode = Configuration.UI_MODE_NIGHT_YES; + assertTrue(config.isNightModeActive()); + config.uiMode = Configuration.UI_MODE_NIGHT_NO; + assertFalse(config.isNightModeActive()); + } + private void dumpDebug(File f, Configuration config) throws Exception { final AtomicFile af = new AtomicFile(f); FileOutputStream fos = af.startWrite(); diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java index 49fb75bf6a45..b8dbfd3186c5 100644 --- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java +++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java @@ -466,179 +466,7 @@ public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceT // expected } } - - @MediumTest - public void testTokenize() throws Exception { - Cursor c; - mDatabase.execSQL("CREATE TABLE tokens (" + - "token TEXT COLLATE unicode," + - "source INTEGER," + - "token_index INTEGER," + - "tag TEXT" + - ");"); - mDatabase.execSQL("CREATE TABLE tokens_no_index (" + - "token TEXT COLLATE unicode," + - "source INTEGER" + - ");"); - - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE(NULL, NULL, NULL, NULL)", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', NULL, NULL, NULL)", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 10, NULL, NULL)", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 10, 'some string', NULL)", null)); - - Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 11, 'some string ok', ' ', 1, 'foo')", null)); - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 11, 'second field', ' ', 1, 'bar')", null)); - - Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens_no_index', 20, 'some string ok', ' ')", null)); - Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens_no_index', 21, 'foo bar baz', ' ', 0)", null)); - - // test Chinese - String chinese = new String("\u4eac\u4ec5 \u5c3d\u5f84\u60ca"); - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 12,'" + chinese + "', ' ', 1)", null)); - - String icustr = new String("Fr\u00e9d\u00e9ric Hj\u00f8nnev\u00e5g"); - - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 13, '" + icustr + "', ' ', 1)", null)); - - Assert.assertEquals(9, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens;", null)); - - String key = DatabaseUtils.getHexCollationKey("Frederic Hjonneva"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("Hjonneva"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("some string ok"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("string"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("ok"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("second field"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("field"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey(chinese); - String[] a = new String[1]; - a[0] = key; - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token= ?", a)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token= ?", a)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token= ?", a)); - a[0] += "*"; - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB ?", a)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB ?", a)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB ?", a)); - - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token= '" + key + "'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token= '" + key + "'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token= '" + key + "'", null)); - - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("\u4eac\u4ec5"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("\u5c3d\u5f84\u60ca"); - Log.d("DatabaseGeneralTest", "key = " + key); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB 'ab*'", null)); - - key = DatabaseUtils.getHexCollationKey("some string ok"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null)); - Assert.assertEquals(20, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("bar"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null)); - Assert.assertEquals(21, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null)); - } - + @MediumTest public void testTransactions() throws Exception { mDatabase.execSQL("CREATE TABLE test (num INTEGER);"); diff --git a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java new file mode 100644 index 000000000000..1f831bb2f9a8 --- /dev/null +++ b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.graphics.Rect; + +import org.junit.Before; +import org.junit.Test; + +public class CutoutSpecificationTest { + private static final String WITHOUT_BIND_CUTOUT_SPECIFICATION = "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h -48\n" + + "v 48\n" + + "h 48\n" + + "z\n" + + "@right\n" + + "@dp"; + private static final String WITH_BIND_CUTOUT_SPECIFICATION = "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h 48\n" + + "v 48\n" + + "h -48\n" + + "z\n" + + "@left\n" + + "@bind_left_cutout\n" + + "@center_vertical\n" + + "M 0,0\n" + + "h -48\n" + + "v 48\n" + + "h 48\n" + + "z\n" + + "@right\n" + + "@bind_right_cutout\n" + + "@dp"; + private static final String CORNER_CUTOUT_SPECIFICATION = "M 0,0\n" + + "h 1\n" + + "v 1\n" + + "h -1\n" + + "z\n" + + "@left\n" + + "@cutout\n" + + "M 0, 0\n" + + "h -2\n" + + "v 2\n" + + "h 2\n" + + "z\n" + + "@right\n" + + "@bind_right_cutout\n" + + "@cutout\n" + + "M 0, 200\n" + + "h 3\n" + + "v -3\n" + + "h -3\n" + + "z\n" + + "@left\n" + + "@bind_left_cutout\n" + + "@bottom\n" + + "M 0, 0\n" + + "h -4\n" + + "v -4\n" + + "h 4\n" + + "z\n" + + "@right\n" + + "@dp"; + + private CutoutSpecification.Parser mParser; + + /** + * Setup the necessary member field used by test methods. + */ + @Before + public void setUp() { + mParser = new CutoutSpecification.Parser(3.5f, 1080, 1920); + } + + @Test + public void parse_nullString_shouldTriggerException() { + assertThrows(NullPointerException.class, () -> mParser.parse(null)); + } + + @Test + public void parse_emptyString_pathShouldBeNull() { + CutoutSpecification cutoutSpecification = mParser.parse(""); + assertThat(cutoutSpecification.getPath()).isNull(); + } + + @Test + public void parse_withoutBindMarker_shouldHaveNoLeftBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getLeftBound()).isNull(); + } + + @Test + public void parse_withoutBindMarker_shouldHaveNoRightBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITHOUT_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getRightBound()).isNull(); + } + + @Test + public void parse_withBindMarker_shouldHaveLeftBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getLeftBound()).isEqualTo(new Rect(0, 960, 168, 1128)); + } + + @Test + public void parse_withBindMarker_shouldHaveRightBound() { + CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION); + assertThat(cutoutSpecification.getRightBound()).isEqualTo(new Rect(912, 960, 1080, 1128)); + } + + @Test + public void parse_tallCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -48, 0\n" + + "L -44.3940446283, 36.0595537175\n" + + "C -43.5582133885, 44.4178661152 -39.6, 48.0 -31.2, 48.0\n" + + "L 31.2, 48.0\n" + + "C 39.6, 48.0 43.5582133885, 44.4178661152 44.3940446283, 36.0595537175\n" + + "L 48, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168); + } + + @Test + public void parse_wideCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, 20.0595537175\n" + + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n" + + "L 56.8, 32.0\n" + + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(504); + } + + @Test + public void parse_narrowCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -24, 0\n" + + "L -21.9940446283, 20.0595537175\n" + + "C -21.1582133885, 28.4178661152 -17.2, 32.0 -8.8, 32.0\n" + + "L 8.8, 32.0\n" + + "C 17.2, 32.0 21.1582133885, 28.4178661152 21.9940446283, 20.0595537175\n" + + "L 24, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().width()).isEqualTo(168); + } + + @Test + public void parse_doubleCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, 20.0595537175\n" + + "C -69.1582133885, 28.4178661152 -65.2, 32.0 -56.8, 32.0\n" + + "L 56.8, 32.0\n" + + "C 65.2, 32.0 69.1582133885, 28.4178661152 69.9940446283, 20.0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@bottom\n" + + "M 0,0\n" + + "L -72, 0\n" + + "L -69.9940446283, -20.0595537175\n" + + "C -69.1582133885, -28.4178661152 -65.2, -32.0 -56.8, -32.0\n" + + "L 56.8, -32.0\n" + + "C 65.2, -32.0 69.1582133885, -28.4178661152 69.9940446283, -20" + + ".0595537175\n" + + "L 72, 0\n" + + "Z\n" + + "@dp"); + + assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(112); + } + + @Test + public void parse_cornerCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n" + + "L -48, 0\n" + + "C -48,48 -48,48 0,48\n" + + "Z\n" + + "@dp\n" + + "@right"); + + assertThat(cutoutSpecification.getTopBound().height()).isEqualTo(168); + } + + @Test + public void parse_holeCutout_shouldBeDone() { + CutoutSpecification cutoutSpecification = mParser.parse("M 20.0,20.0\n" + + "h 136\n" + + "v 136\n" + + "h -136\n" + + "Z\n" + + "@left"); + + assertThat(cutoutSpecification.getSafeInset()).isEqualTo(new Rect(0, 156, 0, 0)); + } + + @Test + public void getSafeInset_shortEdgeIsTopBottom_shouldMatchExpectedInset() { + CutoutSpecification cutoutSpecification = + new CutoutSpecification.Parser(2f, 200, 400) + .parse(CORNER_CUTOUT_SPECIFICATION); + + assertThat(cutoutSpecification.getSafeInset()) + .isEqualTo(new Rect(0, 4, 0, 8)); + } + + @Test + public void getSafeInset_shortEdgeIsLeftRight_shouldMatchExpectedInset() { + CutoutSpecification cutoutSpecification = + new CutoutSpecification.Parser(2f, 400, 200) + .parse(CORNER_CUTOUT_SPECIFICATION); + + assertThat(cutoutSpecification.getSafeInset()) + .isEqualTo(new Rect(6, 0, 8, 0)); + } +} diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java index 12c057f5a91a..02ffc00dcba5 100644 --- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java @@ -20,11 +20,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; import android.annotation.Nullable; import android.os.Parcel; import android.os.UserHandle; -import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; @@ -41,6 +41,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class EditorInfoTest { private static final int TEST_USER_ID = 42; + private static final int LONG_EXP_TEXT_LENGTH = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH * 2; /** * Makes sure that {@code null} {@link EditorInfo#targetInputMethodUser} can be copied via @@ -79,8 +80,8 @@ public class EditorInfoTest { } @Test - public void testNullTextInputComposeInitialSurroundingText() { - final Spannable testText = null; + public void setInitialText_nullInputText_throwsException() { + final CharSequence testText = null; final EditorInfo editorInfo = new EditorInfo(); try { @@ -92,56 +93,75 @@ public class EditorInfoTest { } @Test - public void testNonNullTextInputComposeInitialSurroundingText() { - final Spannable testText = createTestText(/* prependLength= */ 0, - EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); - final EditorInfo editorInfo = new EditorInfo(); + public void setInitialText_cursorAtHead_dividesByCursorPosition() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); - // Cursor at position 0. - int selectionLength = 0; + final EditorInfo editorInfo = new EditorInfo(); + final int selectionLength = 0; editorInfo.initialSelStart = 0; editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; - int expectedTextBeforeCursorLength = 0; - int expectedTextAfterCursorLength = testText.length(); + final int expectedTextBeforeCursorLength = 0; + final int expectedTextAfterCursorLength = testText.length(); editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, expectedTextAfterCursorLength); + } - // Cursor at the end. + @Test + public void setInitialText_cursorAtTail_dividesByCursorPosition() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo editorInfo = new EditorInfo(); + final int selectionLength = 0; editorInfo.initialSelStart = testText.length() - selectionLength; editorInfo.initialSelEnd = testText.length(); - expectedTextBeforeCursorLength = testText.length(); - expectedTextAfterCursorLength = 0; + final int expectedTextBeforeCursorLength = testText.length(); + final int expectedTextAfterCursorLength = 0; editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, expectedTextAfterCursorLength); + } - // Cursor at the middle. - selectionLength = 2; + @Test + public void setInitialText_cursorAtMiddle_dividesByCursorPosition() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo editorInfo = new EditorInfo(); + final int selectionLength = 2; editorInfo.initialSelStart = testText.length() / 2; editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; - expectedTextBeforeCursorLength = editorInfo.initialSelStart; - expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; + final int expectedTextBeforeCursorLength = editorInfo.initialSelStart; + final int expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, expectedTextAfterCursorLength); + } - // Accidentally swap selection start and end. + @Test + public void setInitialText_incorrectCursorOrder_correctsThenDivide() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo editorInfo = new EditorInfo(); + final int selectionLength = 2; editorInfo.initialSelEnd = testText.length() / 2; editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength; + final int expectedTextBeforeCursorLength = testText.length() / 2; + final int expectedTextAfterCursorLength = testText.length() - testText.length() / 2 + - selectionLength; editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, expectedTextAfterCursorLength); + } - // Invalid cursor position. + @Test + public void setInitialText_invalidCursorPosition_returnsNull() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo editorInfo = new EditorInfo(); editorInfo.initialSelStart = -1; editorInfo.setInitialSurroundingText(testText); @@ -153,64 +173,33 @@ public class EditorInfoTest { } @Test - public void testTooLongTextInputComposeInitialSurroundingText() { - final Spannable testText = createTestText(/* prependLength= */ 0, - EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2); + public void setOverSizeInitialText_cursorAtMiddle_dividesProportionately() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2); final EditorInfo editorInfo = new EditorInfo(); - - // Cursor at position 0. - int selectionLength = 0; - editorInfo.initialSelStart = 0; - editorInfo.initialSelEnd = 0 + selectionLength; - int expectedTextBeforeCursorLength = 0; - int expectedTextAfterCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH; - - editorInfo.setInitialSurroundingText(testText); - - assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); - - // Cursor at the end. - editorInfo.initialSelStart = testText.length() - selectionLength; - editorInfo.initialSelEnd = testText.length(); - expectedTextBeforeCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH; - expectedTextAfterCursorLength = 0; - - editorInfo.setInitialSurroundingText(testText); - - assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); - - // Cursor at the middle. - selectionLength = 2; + final int selectionLength = 2; editorInfo.initialSelStart = testText.length() / 2; editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; - expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, + final int expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, (int) (0.8 * (EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH - selectionLength))); - expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + final int expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH - expectedTextBeforeCursorLength - selectionLength; editorInfo.setInitialSurroundingText(testText); assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, expectedTextAfterCursorLength); + } - // Accidentally swap selection start and end. - editorInfo.initialSelEnd = testText.length() / 2; - editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength; - - editorInfo.setInitialSurroundingText(testText); - - assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength, - expectedTextAfterCursorLength); - - // Selection too long, selected text should be dropped. - selectionLength = EditorInfo.MAX_INITIAL_SELECTION_LENGTH + 1; + @Test + public void setOverSizeInitialText_overSizeSelection_dropsSelection() { + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2); + final EditorInfo editorInfo = new EditorInfo(); + final int selectionLength = EditorInfo.MAX_INITIAL_SELECTION_LENGTH + 1; editorInfo.initialSelStart = testText.length() / 2; editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; - expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, + final int expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart, (int) (0.8 * EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH)); - expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; + final int expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd; editorInfo.setInitialSurroundingText(testText); @@ -219,34 +208,59 @@ public class EditorInfoTest { } @Test - public void testTooLongSubTextInputComposeInitialSurroundingText() { - final int prependLength = 5; - final int subTextLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH; - final Spannable fullText = createTestText(prependLength, subTextLength); + public void setInitialSubText_trimmedSubText_dividesByOriginalCursorPosition() { + final String prefixString = "prefix"; + final CharSequence subText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final CharSequence originalText = TextUtils.concat(prefixString, subText); final EditorInfo editorInfo = new EditorInfo(); - // Cursor at the middle. - final int selectionLength = 2; - editorInfo.initialSelStart = fullText.length() / 2; - editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength; - // #prependLength characters will be trimmed out. - final Spannable expectedTextBeforeCursor = createExpectedText(/* startNumber= */0, - editorInfo.initialSelStart - prependLength); - final Spannable expectedSelectedText = createExpectedText( - editorInfo.initialSelStart - prependLength, selectionLength); - final Spannable expectedTextAfterCursor = createExpectedText( - editorInfo.initialSelEnd - prependLength, - fullText.length() - editorInfo.initialSelEnd); - - editorInfo.setInitialSurroundingSubText(fullText.subSequence(prependLength, - fullText.length()), prependLength); + final int selLength = 2; + editorInfo.initialSelStart = originalText.length() / 2; + editorInfo.initialSelEnd = editorInfo.initialSelStart + selLength; + final CharSequence expectedTextBeforeCursor = createExpectedText(/* startNumber= */0, + editorInfo.initialSelStart - prefixString.length()); + final CharSequence expectedSelectedText = createExpectedText( + editorInfo.initialSelStart - prefixString.length(), selLength); + final CharSequence expectedTextAfterCursor = createExpectedText( + editorInfo.initialSelEnd - prefixString.length(), + originalText.length() - editorInfo.initialSelEnd); + + editorInfo.setInitialSurroundingSubText(subText, prefixString.length()); assertTrue(TextUtils.equals(expectedTextBeforeCursor, - editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, - InputConnection.GET_TEXT_WITH_STYLES))); + editorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, anyInt()))); assertTrue(TextUtils.equals(expectedSelectedText, - editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES))); + editorInfo.getInitialSelectedText(anyInt()))); assertTrue(TextUtils.equals(expectedTextAfterCursor, - editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, + editorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, anyInt()))); + } + + @Test + public void initialSurroundingText_wrapIntoParcel_staysIntact() { + // EditorInfo.InitialSurroundingText is not visible to test class. But all its key elements + // must stay intact for its getter methods to return correct value and it will be wrapped + // into its outer class for parcel transfer, therefore we can verify its parcel + // wrapping/unwrapping logic through its outer class. + final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH); + final EditorInfo sourceEditorInfo = new EditorInfo(); + final int selectionLength = 2; + sourceEditorInfo.initialSelStart = testText.length() / 2; + sourceEditorInfo.initialSelEnd = sourceEditorInfo.initialSelStart + selectionLength; + sourceEditorInfo.setInitialSurroundingText(testText); + + final EditorInfo targetEditorInfo = cloneViaParcel(sourceEditorInfo); + + assertTrue(TextUtils.equals( + sourceEditorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES), + targetEditorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES))); + assertTrue(TextUtils.equals( + sourceEditorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES), + targetEditorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES))); + assertTrue(TextUtils.equals( + sourceEditorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, + InputConnection.GET_TEXT_WITH_STYLES), + targetEditorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES))); } @@ -254,12 +268,12 @@ public class EditorInfoTest { @Nullable Integer expectBeforeCursorLength, @Nullable Integer expectSelectionLength, @Nullable Integer expectAfterCursorLength) { final CharSequence textBeforeCursor = - editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, + editorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES); final CharSequence selectedText = editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES); final CharSequence textAfterCursor = - editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH, + editorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, InputConnection.GET_TEXT_WITH_STYLES); if (expectBeforeCursorLength == null) { @@ -281,19 +295,15 @@ public class EditorInfoTest { } } - private static Spannable createTestText(int prependLength, int surroundingLength) { + private static CharSequence createTestText(int surroundingLength) { final SpannableStringBuilder builder = new SpannableStringBuilder(); - for (int i = 0; i < prependLength; i++) { - builder.append("a"); - } - for (int i = 0; i < surroundingLength; i++) { builder.append(Integer.toString(i % 10)); } return builder; } - private static Spannable createExpectedText(int startNumber, int length) { + private static CharSequence createExpectedText(int startNumber, int length) { final SpannableStringBuilder builder = new SpannableStringBuilder(); for (int i = startNumber; i < startNumber + length; i++) { builder.append(Integer.toString(i % 10)); diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java index 5179fa7a6eb5..8dfd6ee92a9a 100644 --- a/services/core/java/com/android/server/GraphicsStatsService.java +++ b/graphics/java/android/graphics/GraphicsStatsService.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.server; +package android.graphics; +import android.annotation.SystemApi; import android.app.AlarmManager; import android.app.AppOpsManager; import android.content.Context; @@ -26,13 +27,14 @@ import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; -import android.os.MemoryFile; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; +import android.os.SharedMemory; import android.os.Trace; import android.os.UserHandle; +import android.system.ErrnoException; import android.util.Log; import android.view.IGraphicsStats; import android.view.IGraphicsStatsCallback; @@ -45,6 +47,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -84,8 +87,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { // This isn't static because we need this to happen after registerNativeMethods, however // the class is loaded (and thus static ctor happens) before that occurs. - private final int ASHMEM_SIZE = nGetAshmemSize(); - private final byte[] ZERO_DATA = new byte[ASHMEM_SIZE]; + private final int mAshmemSize = nGetAshmemSize(); + private final byte[] mZeroData = new byte[mAshmemSize]; private final Context mContext; private final AppOpsManager mAppOps; @@ -97,6 +100,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { private Handler mWriteOutHandler; private boolean mRotateIsScheduled = false; + @SystemApi public GraphicsStatsService(Context context) { mContext = context; mAppOps = context.getSystemService(AppOpsManager.class); @@ -108,7 +112,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { throw new IllegalStateException("Graphics stats directory does not exist: " + mGraphicsStatsDir.getAbsolutePath()); } - HandlerThread bgthread = new HandlerThread("GraphicsStats-disk", Process.THREAD_PRIORITY_BACKGROUND); + HandlerThread bgthread = new HandlerThread("GraphicsStats-disk", + Process.THREAD_PRIORITY_BACKGROUND); bgthread.start(); mWriteOutHandler = new Handler(bgthread.getLooper(), new Handler.Callback() { @@ -159,7 +164,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { active.mCallback.onRotateGraphicsStatsBuffer(); } catch (RemoteException e) { Log.w(TAG, String.format("Failed to notify '%s' (pid=%d) to rotate buffers", - active.mInfo.packageName, active.mPid), e); + active.mInfo.mPackageName, active.mPid), e); } } // Give a few seconds for everyone to rotate before doing the cleanup @@ -167,8 +172,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } @Override - public ParcelFileDescriptor requestBufferForProcess(String packageName, IGraphicsStatsCallback token) - throws RemoteException { + public ParcelFileDescriptor requestBufferForProcess(String packageName, + IGraphicsStatsCallback token) throws RemoteException { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); ParcelFileDescriptor pfd = null; @@ -196,7 +201,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { // current day. // This method is invoked from native code only. @SuppressWarnings({"UnusedDeclaration"}) - private long pullGraphicsStats(boolean lastFullDay) throws RemoteException { + private void pullGraphicsStats(boolean lastFullDay, long pulledData) throws RemoteException { int uid = Binder.getCallingUid(); // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method. @@ -213,13 +218,13 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { long callingIdentity = Binder.clearCallingIdentity(); try { - return pullGraphicsStatsImpl(lastFullDay); + pullGraphicsStatsImpl(lastFullDay, pulledData); } finally { Binder.restoreCallingIdentity(callingIdentity); } } - private long pullGraphicsStatsImpl(boolean lastFullDay) { + private void pullGraphicsStatsImpl(boolean lastFullDay, long pulledData) { long targetDay; if (lastFullDay) { // Get stats from yesterday. Stats stay constant, because the day is over. @@ -235,7 +240,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { buffers = new ArrayList<>(mActive.size()); for (int i = 0; i < mActive.size(); i++) { ActiveBuffer buffer = mActive.get(i); - if (buffer.mInfo.startTime == targetDay) { + if (buffer.mInfo.mStartTime == targetDay) { try { buffers.add(new HistoricalBuffer(buffer)); } catch (IOException ex) { @@ -267,18 +272,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } } } finally { - return nFinishDumpInMemory(dump); - } - } - - private ParcelFileDescriptor getPfd(MemoryFile file) { - try { - if (!file.getFileDescriptor().valid()) { - throw new IllegalStateException("Invalid file descriptor"); - } - return ParcelFileDescriptor.dup(file.getFileDescriptor()); - } catch (IOException ex) { - throw new IllegalStateException("Failed to get PFD from memory file", ex); + nFinishDumpInMemory(dump, pulledData, lastFullDay); } } @@ -286,7 +280,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { int uid, int pid, String packageName, long versionCode) throws RemoteException { ActiveBuffer buffer = fetchActiveBuffersLocked(token, uid, pid, packageName, versionCode); scheduleRotateLocked(); - return getPfd(buffer.mProcessBuffer); + return buffer.getPfd(); } private Calendar normalizeDate(long timestamp) { @@ -301,13 +295,15 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { private File pathForApp(BufferInfo info) { String subPath = String.format("%d/%s/%d/total", - normalizeDate(info.startTime).getTimeInMillis(), info.packageName, info.versionCode); + normalizeDate(info.mStartTime).getTimeInMillis(), info.mPackageName, + info.mVersionCode); return new File(mGraphicsStatsDir, subPath); } private void saveBuffer(HistoricalBuffer buffer) { if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { - Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "saving graphicsstats for " + buffer.mInfo.packageName); + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, + "saving graphicsstats for " + buffer.mInfo.mPackageName); } synchronized (mFileAccessLock) { File path = pathForApp(buffer.mInfo); @@ -317,8 +313,9 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'"); return; } - nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.packageName, buffer.mInfo.versionCode, - buffer.mInfo.startTime, buffer.mInfo.endTime, buffer.mData); + nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mPackageName, + buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime, + buffer.mData); } Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } @@ -365,7 +362,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { HistoricalBuffer data = new HistoricalBuffer(buffer); Message.obtain(mWriteOutHandler, SAVE_BUFFER, data).sendToTarget(); } catch (IOException e) { - Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.packageName, e); + Log.w(TAG, "Failed to copy graphicsstats from " + buffer.mInfo.mPackageName, e); } buffer.closeAllBuffers(); } @@ -386,7 +383,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { if (buffer.mPid == pid && buffer.mUid == uid) { // If the buffer is too old we remove it and return a new one - if (buffer.mInfo.startTime < today) { + if (buffer.mInfo.mStartTime < today) { buffer.binderDied(); break; } else { @@ -410,8 +407,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { HistoricalBuffer buffer = buffers.get(i); File path = pathForApp(buffer.mInfo); skipFiles.add(path); - nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.packageName, - buffer.mInfo.versionCode, buffer.mInfo.startTime, buffer.mInfo.endTime, + nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mPackageName, + buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime, buffer.mData); } return skipFiles; @@ -478,20 +475,20 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { long versionCode, long startTime, long endTime, byte[] data); private static native void nAddToDump(long dump, String path); private static native void nFinishDump(long dump); - private static native long nFinishDumpInMemory(long dump); + private static native void nFinishDumpInMemory(long dump, long pulledData, boolean lastFullDay); private static native void nSaveBuffer(String path, String packageName, long versionCode, long startTime, long endTime, byte[] data); private final class BufferInfo { - final String packageName; - final long versionCode; - long startTime; - long endTime; + final String mPackageName; + final long mVersionCode; + long mStartTime; + long mEndTime; BufferInfo(String packageName, long versionCode, long startTime) { - this.packageName = packageName; - this.versionCode = versionCode; - this.startTime = startTime; + this.mPackageName = packageName; + this.mVersionCode = versionCode; + this.mStartTime = startTime; } } @@ -501,7 +498,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { final int mPid; final IGraphicsStatsCallback mCallback; final IBinder mToken; - MemoryFile mProcessBuffer; + SharedMemory mProcessBuffer; + ByteBuffer mMapping; ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName, long versionCode) @@ -512,8 +510,14 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { mCallback = token; mToken = mCallback.asBinder(); mToken.linkToDeath(this, 0); - mProcessBuffer = new MemoryFile("GFXStats-" + pid, ASHMEM_SIZE); - mProcessBuffer.writeBytes(ZERO_DATA, 0, 0, ASHMEM_SIZE); + try { + mProcessBuffer = SharedMemory.create("GFXStats-" + pid, mAshmemSize); + mMapping = mProcessBuffer.mapReadWrite(); + } catch (ErrnoException ex) { + ex.rethrowAsIOException(); + } + mMapping.position(0); + mMapping.put(mZeroData, 0, mAshmemSize); } @Override @@ -523,20 +527,40 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } void closeAllBuffers() { + if (mMapping != null) { + SharedMemory.unmap(mMapping); + mMapping = null; + } if (mProcessBuffer != null) { mProcessBuffer.close(); mProcessBuffer = null; } } + + ParcelFileDescriptor getPfd() { + try { + return mProcessBuffer.getFdDup(); + } catch (IOException ex) { + throw new IllegalStateException("Failed to get PFD from memory file", ex); + } + } + + void readBytes(byte[] buffer, int count) throws IOException { + if (mMapping == null) { + throw new IOException("SharedMemory has been deactivated"); + } + mMapping.position(0); + mMapping.get(buffer, 0, count); + } } private final class HistoricalBuffer { final BufferInfo mInfo; - final byte[] mData = new byte[ASHMEM_SIZE]; + final byte[] mData = new byte[mAshmemSize]; HistoricalBuffer(ActiveBuffer active) throws IOException { mInfo = active.mInfo; - mInfo.endTime = System.currentTimeMillis(); - active.mProcessBuffer.readBytes(mData, 0, 0, ASHMEM_SIZE); + mInfo.mEndTime = System.currentTimeMillis(); + active.readBytes(mData, mAshmemSize); } } } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 51270f5bcebd..debb38b2c1b0 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -92,9 +92,12 @@ cc_defaults { "libandroidfw", "libcrypto", "libsync", + "libstatspull", + "libstatssocket", ], static_libs: [ "libEGL_blobCache", + "libprotoutil", ], }, host: { @@ -168,7 +171,6 @@ cc_defaults { "renderthread/RenderTask.cpp", "renderthread/TimeLord.cpp", "hwui/AnimatedImageDrawable.cpp", - "hwui/AnimatedImageThread.cpp", "hwui/Bitmap.cpp", "hwui/Canvas.cpp", "hwui/ImageDecoder.cpp", @@ -210,6 +212,7 @@ cc_defaults { android: { srcs: [ + "hwui/AnimatedImageThread.cpp", "pipeline/skia/ATraceMemoryDump.cpp", "pipeline/skia/GLFunctorDrawable.cpp", "pipeline/skia/LayerDrawable.cpp", diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index 4544beae5df8..638de850a6c5 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -15,7 +15,9 @@ */ #include "AnimatedImageDrawable.h" +#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread #include "AnimatedImageThread.h" +#endif #include "utils/TraceUtils.h" @@ -160,8 +162,10 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { } else if (starting) { // The image has animated, and now is being reset. Queue up the first // frame, but keep showing the current frame until the first is ready. +#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread auto& thread = uirenderer::AnimatedImageThread::getInstance(); mNextSnapshot = thread.reset(sk_ref_sp(this)); +#endif } bool finalFrame = false; @@ -187,8 +191,10 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { } if (mRunning && !mNextSnapshot.valid()) { +#ifdef __ANDROID__ // Layoutlib does not support AnimatedImageThread auto& thread = uirenderer::AnimatedImageThread::getInstance(); mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this)); +#endif } if (!drawDirectly) { diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp index c4186174b637..644d5fbd5bf9 100644 --- a/libs/hwui/service/GraphicsStatsService.cpp +++ b/libs/hwui/service/GraphicsStatsService.cpp @@ -26,9 +26,9 @@ #include <sys/types.h> #include <unistd.h> -#include <algorithm> -#include <map> -#include <vector> +#include <android/util/ProtoOutputStream.h> +#include <stats_event.h> +#include <statslog.h> #include "JankTracker.h" #include "protos/graphicsstats.pb.h" @@ -61,7 +61,7 @@ public: } } bool valid() { return mFd != -1; } - operator int() { return mFd; } // NOLINT(google-explicit-constructor) + operator int() { return mFd; } // NOLINT(google-explicit-constructor) private: int mFd; @@ -485,79 +485,82 @@ void GraphicsStatsService::finishDump(Dump* dump) { delete dump; } -class MemOutputStreamLite : public io::ZeroCopyOutputStream { -public: - explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {} - virtual ~MemOutputStreamLite() {} - - virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); } - - virtual void BackUp(int count) override { mImpl.BackUp(count); } - - virtual int64 ByteCount() const override { return mImpl.ByteCount(); } - - bool Flush() { return mImpl.Flush(); } - - void copyData(const DumpMemoryFn& reader, void* param1, void* param2) { - int bufferOffset = 0; - int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize; - int totalDataLeft = totalSize; - for (auto& it : mCopyAdapter.mBuffers) { - int bufferSize = std::min(totalDataLeft, (int)it.size()); // last buffer is not full - reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2); - bufferOffset += bufferSize; - totalDataLeft -= bufferSize; - } - } - -private: - struct MemAdapter : public io::CopyingOutputStream { - // Data is stored in an array of buffers. - // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer. - std::vector<std::vector<unsigned char>> mBuffers; - int mBuffersSize = 0; // total bytes allocated in mBuffers - int mCurrentBufferUnusedSize = 0; // unused bytes in the last buffer mBuffers.back() - unsigned char* mCurrentBuffer = nullptr; // pointer to next free byte in mBuffers.back() +using namespace google::protobuf; - explicit MemAdapter() {} - virtual ~MemAdapter() {} +// Field ids taken from FrameTimingHistogram message in atoms.proto +#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1 +#define FRAME_COUNTS_FIELD_NUMBER 2 + +static void writeCpuHistogram(AStatsEvent* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector<uint8_t> outVector; + proto.serializeToVector(&outVector); + AStatsEvent_writeByteArray(event, outVector.data(), outVector.size()); +} - virtual bool Write(const void* buffer, int size) override { - while (size > 0) { - if (0 == mCurrentBufferUnusedSize) { - mCurrentBufferUnusedSize = - std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000); - mBuffers.emplace_back(); - mBuffers.back().resize(mCurrentBufferUnusedSize); - mCurrentBuffer = mBuffers.back().data(); - mBuffersSize += mCurrentBufferUnusedSize; - } - int dataMoved = std::min(mCurrentBufferUnusedSize, size); - memcpy(mCurrentBuffer, buffer, dataMoved); - mCurrentBufferUnusedSize -= dataMoved; - mCurrentBuffer += dataMoved; - buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved; - size -= dataMoved; - } - return true; - } - }; +static void writeGpuHistogram(AStatsEvent* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector<uint8_t> outVector; + proto.serializeToVector(&outVector); + AStatsEvent_writeByteArray(event, outVector.data(), outVector.size()); +} - MemOutputStreamLite::MemAdapter mCopyAdapter; - io::CopyingOutputStreamAdaptor mImpl; -}; -void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, - void* param2) { - MemOutputStreamLite stream; +void GraphicsStatsService::finishDumpInMemory(Dump* dump, AStatsEventList* data, + bool lastFullDay) { dump->updateProto(); - bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush(); - delete dump; - if (!success) { - return; + auto& serviceDump = dump->proto(); + for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { + auto& stat = serviceDump.stats(stat_index); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, android::util::GRAPHICS_STATS); + AStatsEvent_writeString(event, stat.package_name().c_str()); + AStatsEvent_writeInt64(event, (int64_t)stat.version_code()); + AStatsEvent_writeInt64(event, (int64_t)stat.stats_start()); + AStatsEvent_writeInt64(event, (int64_t)stat.stats_end()); + AStatsEvent_writeInt32(event, (int32_t)stat.pipeline()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().total_frames()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_vsync_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().high_input_latency_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_ui_thread_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_draw_count()); + AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_deadline_count()); + writeCpuHistogram(event, stat); + writeGpuHistogram(event, stat); + // TODO: fill in UI mainline module version, when the feature is available. + AStatsEvent_writeInt64(event, (int64_t)0); + AStatsEvent_writeBool(event, !lastFullDay); + AStatsEvent_build(event); } - stream.copyData(reader, param1, param2); } + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h index 4bed96330a52..59e21d039c9d 100644 --- a/libs/hwui/service/GraphicsStatsService.h +++ b/libs/hwui/service/GraphicsStatsService.h @@ -20,6 +20,7 @@ #include "JankTracker.h" #include "utils/Macros.h" +#include <stats_pull_atom_callback.h> namespace android { namespace uirenderer { @@ -27,9 +28,6 @@ namespace protos { class GraphicsStatsProto; } -typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize, - void* param1, void* param2); - /* * The exported entry points used by GraphicsStatsService.java in f/b/services/core * @@ -56,8 +54,8 @@ public: int64_t startTime, int64_t endTime, const ProfileData* data); ANDROID_API static void addToDump(Dump* dump, const std::string& path); ANDROID_API static void finishDump(Dump* dump); - ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, - void* param2); + ANDROID_API static void finishDumpInMemory(Dump* dump, AStatsEventList* data, + bool lastFullDay); // Visible for testing static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index e4348f2a9b21..3b494e9129db 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -251,19 +251,24 @@ void PointerController::unfade(Transition transition) { void PointerController::setPresentation(Presentation presentation) { AutoMutex _l(mLock); - if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) { - mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, - &mLocked.animationResources, mLocked.viewport.displayId); + if (mLocked.presentation == presentation) { + return; } - if (mLocked.presentation != presentation) { - mLocked.presentation = presentation; - mLocked.presentationChanged = true; + mLocked.presentation = presentation; + mLocked.presentationChanged = true; - if (presentation != PRESENTATION_SPOT) { - fadeOutAndReleaseAllSpotsLocked(); - } + if (!mLocked.viewport.isValid()) { + return; + } + if (presentation == PRESENTATION_POINTER) { + if (mLocked.additionalMouseResources.empty()) { + mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, + &mLocked.animationResources, + mLocked.viewport.displayId); + } + fadeOutAndReleaseAllSpotsLocked(); updatePointerLocked(); } } @@ -285,6 +290,9 @@ void PointerController::setSpots(const PointerCoords* spotCoords, #endif AutoMutex _l(mLock); + if (!mLocked.viewport.isValid()) { + return; + } std::vector<Spot*> newSpots; std::map<int32_t, std::vector<Spot*>>::const_iterator iter = @@ -331,6 +339,9 @@ void PointerController::clearSpots() { #endif AutoMutex _l(mLock); + if (!mLocked.viewport.isValid()) { + return; + } fadeOutAndReleaseAllSpotsLocked(); } @@ -752,6 +763,10 @@ void PointerController::fadeOutAndReleaseAllSpotsLocked() { } void PointerController::loadResourcesLocked() REQUIRES(mLock) { + if (!mLocked.viewport.isValid()) { + return; + } + mPolicy->loadPointerResources(&mResources, mLocked.viewport.displayId); mPolicy->loadPointerIcon(&mLocked.pointerIcon, mLocked.viewport.displayId); diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index b36406d6a703..a15742671dc7 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -39,8 +39,8 @@ enum TestCursorType { using ::testing::AllOf; using ::testing::Field; -using ::testing::NiceMock; using ::testing::Mock; +using ::testing::NiceMock; using ::testing::Return; using ::testing::Test; @@ -57,12 +57,20 @@ public: virtual int32_t getDefaultPointerIconId() override; virtual int32_t getCustomPointerIconId() override; + bool allResourcesAreLoaded(); + bool noResourcesAreLoaded(); + private: void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType); + + bool pointerIconLoaded{false}; + bool pointerResourcesLoaded{false}; + bool additionalMouseResourcesLoaded{false}; }; void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) { loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT); + pointerIconLoaded = true; } void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources, @@ -70,6 +78,7 @@ void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER); loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH); loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR); + pointerResourcesLoaded = true; } void MockPointerControllerPolicyInterface::loadAdditionalMouseResources( @@ -91,6 +100,8 @@ void MockPointerControllerPolicyInterface::loadAdditionalMouseResources( anim.durationPerFrame = 10; (*outResources)[cursorType] = icon; (*outAnimationResources)[cursorType] = anim; + + additionalMouseResourcesLoaded = true; } int32_t MockPointerControllerPolicyInterface::getDefaultPointerIconId() { @@ -101,18 +112,27 @@ int32_t MockPointerControllerPolicyInterface::getCustomPointerIconId() { return CURSOR_TYPE_CUSTOM; } +bool MockPointerControllerPolicyInterface::allResourcesAreLoaded() { + return pointerIconLoaded && pointerResourcesLoaded && additionalMouseResourcesLoaded; +} + +bool MockPointerControllerPolicyInterface::noResourcesAreLoaded() { + return !(pointerIconLoaded || pointerResourcesLoaded || additionalMouseResourcesLoaded); +} + void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* icon, int32_t type) { icon->style = type; std::pair<float, float> hotSpot = getHotSpotCoordinatesForType(type); icon->hotSpotX = hotSpot.first; icon->hotSpotY = hotSpot.second; } - class PointerControllerTest : public Test { protected: PointerControllerTest(); ~PointerControllerTest(); + void ensureDisplayViewportIsSet(); + sp<MockSprite> mPointerSprite; sp<MockPointerControllerPolicyInterface> mPolicy; sp<MockSpriteController> mSpriteController; @@ -141,7 +161,14 @@ PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<Moc .WillOnce(Return(mPointerSprite)); mPointerController = new PointerController(mPolicy, mLooper, mSpriteController); +} + +PointerControllerTest::~PointerControllerTest() { + mRunning.store(false, std::memory_order_relaxed); + mThread.join(); +} +void PointerControllerTest::ensureDisplayViewportIsSet() { DisplayViewport viewport; viewport.displayId = ADISPLAY_ID_DEFAULT; viewport.logicalRight = 1600; @@ -151,11 +178,9 @@ PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<Moc viewport.deviceWidth = 400; viewport.deviceHeight = 300; mPointerController->setDisplayViewport(viewport); -} -PointerControllerTest::~PointerControllerTest() { - mRunning.store(false, std::memory_order_relaxed); - mThread.join(); + // The first call to setDisplayViewport should trigger the loading of the necessary resources. + EXPECT_TRUE(mPolicy->allResourcesAreLoaded()); } void PointerControllerTest::loopThread() { @@ -167,6 +192,7 @@ void PointerControllerTest::loopThread() { } TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) { + ensureDisplayViewportIsSet(); mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT); @@ -181,6 +207,7 @@ TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) { } TEST_F(PointerControllerTest, updatePointerIcon) { + ensureDisplayViewportIsSet(); mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); int32_t type = CURSOR_TYPE_ADDITIONAL; @@ -196,6 +223,7 @@ TEST_F(PointerControllerTest, updatePointerIcon) { } TEST_F(PointerControllerTest, setCustomPointerIcon) { + ensureDisplayViewportIsSet(); mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); int32_t style = CURSOR_TYPE_CUSTOM; @@ -217,4 +245,18 @@ TEST_F(PointerControllerTest, setCustomPointerIcon) { mPointerController->setCustomPointerIcon(icon); } +TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) { + mPointerController->setPresentation(PointerController::PRESENTATION_POINTER); + mPointerController->setSpots(nullptr, nullptr, BitSet32(), -1); + mPointerController->clearSpots(); + mPointerController->setPosition(1.0f, 1.0f); + mPointerController->move(1.0f, 1.0f); + mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + mPointerController->fade(PointerController::TRANSITION_IMMEDIATE); + + EXPECT_TRUE(mPolicy->noResourcesAreLoaded()); + + ensureDisplayViewportIsSet(); +} + } // namespace android diff --git a/libs/services/Android.bp b/libs/services/Android.bp index 9b047ca22d19..1e621079b532 100644 --- a/libs/services/Android.bp +++ b/libs/services/Android.bp @@ -20,7 +20,6 @@ cc_library_shared { ":IDropBoxManagerService.aidl", "src/content/ComponentName.cpp", "src/os/DropBoxManager.cpp", - "src/os/StatsDimensionsValue.cpp", ], shared_libs: [ diff --git a/libs/services/include/android/os/StatsDimensionsValue.h b/libs/services/include/android/os/StatsDimensionsValue.h deleted file mode 100644 index cc0b05644f2c..000000000000 --- a/libs/services/include/android/os/StatsDimensionsValue.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#ifndef STATS_DIMENSIONS_VALUE_H -#define STATS_DIMENSIONS_VALUE_H - -#include <binder/Parcel.h> -#include <binder/Parcelable.h> -#include <binder/Status.h> -#include <utils/String16.h> -#include <vector> - -namespace android { -namespace os { - -// Represents a parcelable object. Used to send data from statsd to StatsCompanionService.java. -class StatsDimensionsValue : public android::Parcelable { -public: - StatsDimensionsValue(); - - StatsDimensionsValue(int32_t field, String16 value); - StatsDimensionsValue(int32_t field, int32_t value); - StatsDimensionsValue(int32_t field, int64_t value); - StatsDimensionsValue(int32_t field, bool value); - StatsDimensionsValue(int32_t field, float value); - StatsDimensionsValue(int32_t field, std::vector<StatsDimensionsValue> value); - - virtual ~StatsDimensionsValue(); - - virtual android::status_t writeToParcel(android::Parcel* out) const override; - virtual android::status_t readFromParcel(const android::Parcel* in) override; - -private: - // Keep constants in sync with android/os/StatsDimensionsValue.java - // and stats_log.proto's DimensionValue. - static const int kStrValueType = 2; - static const int kIntValueType = 3; - static const int kLongValueType = 4; - static const int kBoolValueType = 5; - static const int kFloatValueType = 6; - static const int kTupleValueType = 7; - - int32_t mField; - int32_t mValueType; - - // This isn't very clever, but it isn't used for long-term storage, so it'll do. - String16 mStrValue; - int32_t mIntValue; - int64_t mLongValue; - bool mBoolValue; - float mFloatValue; - std::vector<StatsDimensionsValue> mTupleValue; -}; - -} // namespace os -} // namespace android - -#endif // STATS_DIMENSIONS_VALUE_H diff --git a/libs/services/src/os/StatsDimensionsValue.cpp b/libs/services/src/os/StatsDimensionsValue.cpp deleted file mode 100644 index 0052e0baa905..000000000000 --- a/libs/services/src/os/StatsDimensionsValue.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "StatsDimensionsValue" - -#include "android/os/StatsDimensionsValue.h" - -#include <cutils/log.h> - -using android::Parcel; -using android::Parcelable; -using android::status_t; -using std::vector; - -namespace android { -namespace os { - -StatsDimensionsValue::StatsDimensionsValue() {}; - -StatsDimensionsValue::StatsDimensionsValue(int32_t field, String16 value) : - mField(field), - mValueType(kStrValueType), - mStrValue(value) { -} -StatsDimensionsValue::StatsDimensionsValue(int32_t field, int32_t value) : - mField(field), - mValueType(kIntValueType), - mIntValue(value) { -} -StatsDimensionsValue::StatsDimensionsValue(int32_t field, int64_t value) : - mField(field), - mValueType(kLongValueType), - mLongValue(value) { -} -StatsDimensionsValue::StatsDimensionsValue(int32_t field, bool value) : - mField(field), - mValueType(kBoolValueType), - mBoolValue(value) { -} -StatsDimensionsValue::StatsDimensionsValue(int32_t field, float value) : - mField(field), - mValueType(kFloatValueType), - mFloatValue(value) { -} -StatsDimensionsValue::StatsDimensionsValue(int32_t field, vector<StatsDimensionsValue> value) : - mField(field), - mValueType(kTupleValueType), - mTupleValue(value) { -} - -StatsDimensionsValue::~StatsDimensionsValue() {} - -status_t -StatsDimensionsValue::writeToParcel(Parcel* out) const { - status_t err ; - - err = out->writeInt32(mField); - if (err != NO_ERROR) { - return err; - } - err = out->writeInt32(mValueType); - if (err != NO_ERROR) { - return err; - } - switch (mValueType) { - case kStrValueType: - err = out->writeString16(mStrValue); - break; - case kIntValueType: - err = out->writeInt32(mIntValue); - break; - case kLongValueType: - err = out->writeInt64(mLongValue); - break; - case kBoolValueType: - err = out->writeBool(mBoolValue); - break; - case kFloatValueType: - err = out->writeFloat(mFloatValue); - break; - case kTupleValueType: - { - int sz = mTupleValue.size(); - err = out->writeInt32(sz); - if (err != NO_ERROR) { - return err; - } - for (int i = 0; i < sz; ++i) { - err = mTupleValue[i].writeToParcel(out); - if (err != NO_ERROR) { - return err; - } - } - } - break; - default: - err = UNKNOWN_ERROR; - break; - } - return err; -} - -status_t -StatsDimensionsValue::readFromParcel(const Parcel* in) -{ - // Implement me if desired. We don't currently use this. - ALOGE("Cannot do c++ StatsDimensionsValue.readFromParcel(); it is not implemented."); - (void)in; // To prevent compile error of unused parameter 'in' - return UNKNOWN_ERROR; -} - -} // namespace os -} // namespace android diff --git a/core/java/android/os/StatsDimensionsValue.aidl b/location/java/android/location/GnssRequest.aidl index 81a14a4b67d9..581abcce6651 100644 --- a/core/java/android/os/StatsDimensionsValue.aidl +++ b/location/java/android/location/GnssRequest.aidl @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2018, The Android Open Source Project +/* + * Copyright (C) 2020, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,9 @@ * limitations under the License. */ -package android.os; +package android.location; -/** @hide */ -parcelable StatsDimensionsValue cpp_header "android/os/StatsDimensionsValue.h";
\ No newline at end of file +/** + * @hide + */ +parcelable GnssRequest; diff --git a/location/java/android/location/GnssRequest.java b/location/java/android/location/GnssRequest.java new file mode 100644 index 000000000000..2afb265f638c --- /dev/null +++ b/location/java/android/location/GnssRequest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class contains extra parameters to pass to a GNSS provider implementation. + * @hide + */ +@SystemApi +public final class GnssRequest implements Parcelable { + private final boolean mFullTracking; + + /** + * Creates a {@link GnssRequest} with a full list of parameters. + */ + private GnssRequest(boolean fullTracking) { + mFullTracking = fullTracking; + } + + /** + * Represents whether to enable full GNSS tracking. + * + * <p>If true, GNSS chipset switches off duty cycling. In such a mode, no clock + * discontinuities are expected, and when supported, carrier phase should be continuous in + * good signal conditions. All non-blacklisted, healthy constellations, satellites and + * frequency bands that the chipset supports must be reported in this mode. The GNSS chipset + * is allowed to consume more power in this mode. If false, GNSS chipset optimizes power via + * duty cycling, constellations and frequency limits, etc. + */ + public boolean isFullTracking() { + return mFullTracking; + } + + @NonNull + public static final Creator<GnssRequest> CREATOR = + new Creator<GnssRequest>() { + @Override + @NonNull + public GnssRequest createFromParcel(@NonNull Parcel parcel) { + return new GnssRequest(parcel.readBoolean()); + } + + @Override + public GnssRequest[] newArray(int i) { + return new GnssRequest[i]; + } + }; + + @NonNull + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("GnssRequest["); + s.append("FullTracking=").append(mFullTracking); + s.append(']'); + return s.toString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof GnssRequest)) return false; + + GnssRequest other = (GnssRequest) obj; + if (mFullTracking != other.mFullTracking) return false; + + return true; + } + + @Override + public int hashCode() { + return mFullTracking ? 1 : 0; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeBoolean(mFullTracking); + } + + /** Builder for {@link GnssRequest} */ + public static final class Builder { + private boolean mFullTracking; + + /** + * Constructs a {@link Builder} instance. + */ + public Builder() { + } + + /** + * Constructs a {@link Builder} instance by copying a {@link GnssRequest}. + */ + public Builder(@NonNull GnssRequest request) { + mFullTracking = request.isFullTracking(); + } + + /** + * Set the value of whether to enable full GNSS tracking, which is false by default. + * + * <p>If true, GNSS chipset switches off duty cycling. In such a mode, no clock + * discontinuities are expected, and when supported, carrier phase should be continuous in + * good signal conditions. All non-blacklisted, healthy constellations, satellites and + * frequency bands that the chipset supports must be reported in this mode. The GNSS chipset + * is allowed to consume more power in this mode. If false, GNSS chipset optimizes power via + * duty cycling, constellations and frequency limits, etc. + * + * <p>Full tracking requests always override non-full tracking requests. If any full + * tracking request occurs, all listeners on the device will receive full tracking GNSS + * measurements. + */ + @NonNull public Builder setFullTracking(boolean value) { + mFullTracking = value; + return this; + } + + /** Builds a {@link GnssRequest} instance as specified by this builder. */ + @NonNull + public GnssRequest build() { + return new GnssRequest(mFullTracking); + } + } +} diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 1c10edb11293..7e6486cc933e 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -2196,6 +2196,30 @@ public class LocationManager { } /** + * Registers a GNSS Measurement callback. + * + * @param request extra parameters to pass to GNSS measurement provider. For example, if {@link + * GnssRequest#isFullTrackingEnabled()} is true, GNSS chipset switches off duty + * cycling. + * @param executor the executor that the callback runs on. + * @param callback a {@link GnssMeasurementsEvent.Callback} object to register. + * @return {@code true} if the callback was added successfully, {@code false} otherwise. + * @throws IllegalArgumentException if request is null + * @throws IllegalArgumentException if executor is null + * @throws IllegalArgumentException if callback is null + * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present + * @hide + */ + @SystemApi + @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, LOCATION_HARDWARE}) + public boolean registerGnssMeasurementsCallback( + @NonNull GnssRequest request, + @NonNull @CallbackExecutor Executor executor, + @NonNull GnssMeasurementsEvent.Callback callback) { + throw new RuntimeException(); + } + + /** * Injects GNSS measurement corrections into the GNSS chipset. * * @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 767b67b7e885..d2379757f226 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1457,6 +1457,9 @@ public class ExifInterface { private int mRw2JpgFromRawOffset; private boolean mIsSupportedFile; private boolean mModified; + // XMP data can be contained as either part of the EXIF data (tag number 700), or as a + // separate data marker (a separate MARKER_APP1). + private boolean mXmpIsFromSeparateMarker; // Pattern to check non zero timestamp private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*"); @@ -2837,10 +2840,12 @@ public class ExifInterface { final long offset = start + IDENTIFIER_XMP_APP1.length; final byte[] value = Arrays.copyOfRange(bytes, IDENTIFIER_XMP_APP1.length, bytes.length); - + // TODO: check if ignoring separate XMP data when tag 700 already exists is + // valid. if (getAttribute(TAG_XMP) == null) { mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute( IFD_FORMAT_BYTE, value.length, offset, value)); + mXmpIsFromSeparateMarker = true; } } break; @@ -3445,11 +3450,24 @@ public class ExifInterface { } dataOutputStream.writeByte(MARKER_SOI); + // Remove XMP data if it is from a separate marker (IDENTIFIER_XMP_APP1, not + // IDENTIFIER_EXIF_APP1) + // Will re-add it later after the rest of the file is written + ExifAttribute xmpAttribute = null; + if (getAttribute(TAG_XMP) != null && mXmpIsFromSeparateMarker) { + xmpAttribute = (ExifAttribute) mAttributes[IFD_TYPE_PRIMARY].remove(TAG_XMP); + } + // Write EXIF APP1 segment dataOutputStream.writeByte(MARKER); dataOutputStream.writeByte(MARKER_APP1); writeExifSegment(dataOutputStream); + // Re-add previously removed XMP data. + if (xmpAttribute != null) { + mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, xmpAttribute); + } + byte[] bytes = new byte[4096]; while (true) { diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index b1f930d0f2cc..2c1fdab9da01 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -247,13 +247,23 @@ public class MediaRouter2Manager { Objects.requireNonNull(packageName, "packageName must not be null"); Objects.requireNonNull(route, "route must not be null"); + boolean transferred = false; + //TODO: instead of release all controllers, add an API to specify controllers that + // should be released (or is the system controller). for (RoutingController controller : getRoutingControllers(packageName)) { - if (controller.getSessionInfo().getTransferrableRoutes().contains(route.getId())) { + if (!transferred && controller.getSessionInfo().getTransferrableRoutes() + .contains(route.getId())) { controller.transferToRoute(route); - return; + transferred = true; + } else if (!controller.getSessionInfo().isSystemSession()) { + controller.release(); } } + if (transferred) { + return; + } + Client client; synchronized (sLock) { client = mClient; diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 870c1b4a3558..486c0c25a737 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -266,8 +266,12 @@ public final class MediaSession { * playback after the session has been stopped. If your app is started in * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via * the pending intent. + * <p> + * The pending intent is recommended to be explicit to follow the security recommendation of + * {@link PendingIntent#getActivity}. * * @param mbr The {@link PendingIntent} to send the media button event to. + * @see PendingIntent#getActivity */ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { try { diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 3561f8393eea..9b183a3e0e92 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -244,7 +244,7 @@ public class Tuner implements AutoCloseable { * * <p> * Tuner events are started when {@link #tune(FrontendSettings)} is called and end when {@link - * #stopTune()} is called. + * #cancelTuning()} is called. * * @param eventListener receives tune events. * @throws SecurityException if the caller does not have appropriate permissions. @@ -309,7 +309,7 @@ public class Tuner implements AutoCloseable { */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result - public int stopTune() { + public int cancelTuning() { TunerUtils.checkTunerPermission(mContext); return nativeStopTune(); } @@ -322,8 +322,8 @@ public class Tuner implements AutoCloseable { * @param settings A {@link FrontendSettings} to configure the frontend. * @param scanType The scan type. * @throws SecurityException if the caller does not have appropriate permissions. - * @throws IllegalStateException if {@code scan} is called again before {@link #stopScan()} is - * called. + * @throws IllegalStateException if {@code scan} is called again before + * {@link #cancelScanning()} is called. */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result @@ -354,7 +354,7 @@ public class Tuner implements AutoCloseable { */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result - public int stopScan() { + public int cancelScanning() { TunerUtils.checkTunerPermission(mContext); int retVal = nativeStopScan(); mScanCallback = null; diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java index c1b17d3615bb..63de03344219 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java @@ -41,8 +41,7 @@ public class FrontendStatus { FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL, FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID, FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA, - FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN, - FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER, + FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_MER, FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY, FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO}) @Retention(RetentionPolicy.SOURCE) @@ -125,18 +124,6 @@ public class FrontendStatus { public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR = Constants.FrontendStatusType.LAYER_ERROR; /** - * CN value by VBER. - */ - public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN; - /** - * CN value by LBER. - */ - public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN; - /** - * CN value by XER. - */ - public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN; - /** * Modulation Error Ratio. */ public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER; @@ -223,9 +210,6 @@ public class FrontendStatus { private Integer mAgc; private Boolean mIsLnaOn; private boolean[] mIsLayerErrors; - private Integer mVberCn; - private Integer mLberCn; - private Integer mXerCn; private Integer mMer; private Integer mFreqOffset; private Integer mHierarchy; @@ -403,33 +387,6 @@ public class FrontendStatus { return mIsLayerErrors; } /** - * Gets CN value by VBER in thousandths of a deciBel (0.001dB). - */ - public int getVberCn() { - if (mVberCn == null) { - throw new IllegalStateException(); - } - return mVberCn; - } - /** - * Gets CN value by LBER in thousandths of a deciBel (0.001dB). - */ - public int getLberCn() { - if (mLberCn == null) { - throw new IllegalStateException(); - } - return mLberCn; - } - /** - * Gets CN value by XER in thousandths of a deciBel (0.001dB). - */ - public int getXerCn() { - if (mXerCn == null) { - throw new IllegalStateException(); - } - return mXerCn; - } - /** * Gets Modulation Error Ratio in thousandths of a deciBel (0.001dB). */ public int getMer() { diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java index f90144bc97ef..8105c74cfb62 100644 --- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java +++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java @@ -67,7 +67,7 @@ public interface ScanCallback { /** Frontend hierarchy. */ void onHierarchy(@DvbtFrontendSettings.Hierarchy int hierarchy); - /** Frontend hierarchy. */ + /** Frontend signal type. */ void onSignalType(@AnalogFrontendSettings.SignalType int signalType); } diff --git a/media/tests/TunerTest/OWNERS b/media/tests/TunerTest/OWNERS new file mode 100644 index 000000000000..73ea663aa37e --- /dev/null +++ b/media/tests/TunerTest/OWNERS @@ -0,0 +1,4 @@ +amyjojo@google.com +nchalko@google.com +quxiangfang@google.com +shubang@google.com diff --git a/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml index a06dc546f85c..c4d8f35cec71 100644 --- a/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml +++ b/packages/Incremental/NativeAdbDataLoader/AndroidManifest.xml @@ -29,7 +29,7 @@ <service android:enabled="true" android:name="com.android.incremental.nativeadb.NativeAdbDataLoaderService" android:label="@string/app_name" - android:exported="true"> + android:exported="false"> <intent-filter> <action android:name="android.intent.action.LOAD_DATA" /> </intent-filter> diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java index bd5b79594c19..c4e41c87564f 100644 --- a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java +++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java @@ -16,6 +16,8 @@ package com.android.incremental.nativeadb; +import android.annotation.NonNull; +import android.content.pm.DataLoaderParams; import android.service.dataloader.DataLoaderService; /** This code is used for testing only. */ @@ -26,7 +28,7 @@ public class NativeAdbDataLoaderService extends DataLoaderService { } @Override - public DataLoader onCreateDataLoader() { + public DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) { return null; } } diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index b31841d82d0e..1a015a697ee0 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Gekoppel via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Beskikbaar via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te meld"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Geen internet nie"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Daar kan nie by private DNS-bediener ingegaan word nie"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet nie"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rooi-groen)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (blou-geel)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Kleurregstelling"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Kleurregstelling help mense met kleurblindheid om akkurater kleure te sien"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Geneutraliseer deur <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g> oor"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> tot battery gelaai is"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laai"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laai tans"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laai nie"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ingeprop; kan nie op die oomblik laai nie"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Vol"</string> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 4955ad8fda29..05f0e1bd94d9 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"በ <xliff:g id="NAME">%1$s</xliff:g> በኩል ተገናኝተዋል"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"በ%1$s በኩል የሚገኝ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ለመመዝገብ መታ ያድርጉ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ምንም በይነመረብ የለም"</string> <string name="private_dns_broken" msgid="1984159464346556931">"የግል ዲኤንኤስ አገልጋይ ሊደረስበት አይችልም"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"የተገደበ ግንኙነት"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ምንም በይነመረብ የለም"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ፕሮታኖማሊ (ቀይ-አረንጓዴ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ትራይታኖማሊ (ሰማያዊ-ቢጫ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"የቀለም ማስተካከያ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ቀለም ማስተካከያ የቀለም ማየት የማይችሉ ሰዎች ተጨማሪ ትክክለኛ ቀለማትን እንዲመለከቱ ያስችላቸዋል"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"በ<xliff:g id="TITLE">%1$s</xliff:g> ተሽሯል"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ገደማ ቀርቷል"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ኃይል እስከሚሞላ ድረስ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ያልታወቀ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ኃይል በመሙላት ላይ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ኃይል በመሙላት ላይ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ባትሪ እየሞላ አይደለም"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ተሰክቷል፣ አሁን ኃይል መሙላት አይቻልም"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ሙሉነው"</string> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 7b26be4f4c14..07befebde7ec 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"تم الاتصال عبر <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="available_via_passpoint" msgid="1716000261192603682">"متوفرة عبر %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"انقر للاشتراك."</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"لا يتوفر اتصال إنترنت."</string> <string name="private_dns_broken" msgid="1984159464346556931">"لا يمكن الوصول إلى خادم أسماء نظام نطاقات خاص"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"لا يتوفر اتصال إنترنت."</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"غطش الأحمر (الأحمر والأخضر)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"غمش الأزرق (الأزرق والأصفر)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"تصحيح الألوان"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"تساعد ميزة تصحيح الألوان المصابين بعمى الألوان على رؤية الألوان بدقة أكبر"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"تم الاستبدال بـ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"يتبقى <xliff:g id="TIME_REMAINING">%1$s</xliff:g> تقريبًا"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> إلى أن يتم شحن الجهاز بالكامل"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"غير معروف"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"جارٍ الشحن"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"جارٍ الشحن"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"لا يتم الشحن"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"تم التوصيل، ولكن يتعذّر الشحن الآن"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ممتلئة"</string> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index dcebe5bd9b6c..4d0d8cc4ba01 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>ৰ জৰিয়তে সংযুক্ত"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$sৰ মাধ্যমেৰে উপলব্ধ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ছাইন আপ কৰিবলৈ টিপক"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ইণ্টাৰনেট সংযোগ নাই"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত DNS ছাৰ্ভাৰ এক্সেছ কৰিব নোৱাৰি"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ইণ্টাৰনেট সংযোগ সীমিত"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইণ্টাৰনেট সংযোগ নাই"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"প্ৰ’টানোমালি (ৰঙা-সেউজীয়া)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ট্ৰাইটান\'মেলী (নীলা-হালধীয়া)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ৰং শুধৰণী"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ৰং শুধৰণী কৰা কার্যই বর্ণান্ধলোকসকলক ৰংবোৰ অধিক সঠিককৈ দেখাত সহায় কৰে"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>ৰ দ্বাৰা অগ্ৰাহ্য কৰা হৈছে"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"প্রায় <xliff:g id="TIME_REMAINING">%1$s</xliff:g> বাকী আছে"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> চাৰ্জ হ\'বলৈ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"অজ্ঞাত"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"চাৰ্জ কৰি থকা হৈছে"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"চ্চাৰ্জ হৈ আছে"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"চ্চাৰ্জ কৰা নাই"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"প্লাগ কৰি থোৱা হৈছে, এই মুহূৰ্তত চ্চাৰ্জ কৰিব নোৱাৰি"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"পূৰ্ণ"</string> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index 7f3db37f2a25..73eb48a02324 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ilə qoşulub"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s vasitəsilə əlçatandır"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Qeydiyyatdan keçmək üçün klikləyin"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"İnternet yoxdur"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Özəl DNS serverinə giriş mümkün deyil"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Məhdud bağlantı"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yoxdur"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaliya (qırmızı-yaşıl)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaliya (göy-sarı)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Rəng düzəlişi"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Rəng düzəlişi rəng korluğu olanların daha yaxşı görməsinə kömək edir"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tərəfindən qəbul edilmir"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Təxminən <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qalıb"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - Enerjinin dolmasına <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Naməlum"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Enerji doldurma"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"enerji yığır"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Doldurulmur"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Cihaz hazırda batareya yığa bilmir"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Tam"</string> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index b789cb0dbe5a..dff657e34e69 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupna je preko pristupne tačke %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrovali"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema interneta"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Pristup privatnom DNS serveru nije uspeo"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno-zeleno)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo-žuto)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcija boja"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcija boja pomaže ljudima koji su daltonisti da preciznije vide boje"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamenjuje ga <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napuniće se za <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Puni se"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"puni se"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključeno je, ali punjenje trenutno nije moguće"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index ce191eb59676..12c21a6d7ba4 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Падключана праз праграму \"<xliff:g id="NAME">%1$s</xliff:g>\""</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Даступна праз %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Націсніце, каб зарэгістравацца"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Не падключана да інтэрнэту"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Не ўдалося атрымаць доступ да прыватнага DNS-сервера"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Абмежаваныя магчымасці падключэння"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Не падключана да інтэрнэту"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Пратанамалія (чырвоны-зялёны)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Трытанамалія (сіні-жоўты)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Карэкцыя колеру"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Карэкцыя колеру дазваляе людзям з парушэннямі колеравага зроку лепш распазнаваць выявы на экране"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Перавызначаны <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Зараду хопіць прыблізна на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> да поўнай зарадкі"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Невядома"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарадка"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ідзе зарадка"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не зараджаецца"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Падключана да сеткі сілкавання, зарадзіць зараз немагчыма"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Акумулятар зараджаны"</string> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 3e6f77db29c1..6fda5b358a7c 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Установена е връзка през <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Мрежата е достъпна през „%1$s“"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Докоснете, за да се регистрирате"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Няма интернет"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се осъществи достъп до частния DNS сървър"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена връзка"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Няма връзка с интернет"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (червено – зелено)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (синьо – жълто)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекция на цветове"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Коригирането на цветовете помага на хората с цветна слепота да виждат по-точни цветове"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Заменено от „<xliff:g id="TITLE">%1$s</xliff:g>“"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Още около <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до пълно зареждане"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарежда се"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"зарежда се"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се зарежда"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Включена в захранването, в момента не се зарежда"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Пълна"</string> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 7f6938ac8de2..be2d1e0482be 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-এর মাধ্যমে কানেক্ট করা আছে"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s এর মাধ্যমে উপলব্ধ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"সাইন-আপ করতে ট্যাপ করুন"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ইন্টারনেট কানেকশন নেই"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ব্যক্তিগত ডিএনএস সার্ভার অ্যাক্সেস করা যাবে না"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"সীমিত কানেকশন"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ইন্টারনেট কানেকশন নেই"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"প্রোটানোম্যালি (লাল-সবুজ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ট্রিট্যানোম্যালি (নীল-হলুদ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"রঙ সংশোধন"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"রঙ অ্যাডজাস্ট করার সেটিংস, বর্ণান্ধতা আছে এমন ব্যক্তিদের আরও সঠিকভাবে রঙ চিনতে সাহায্য করে"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> এর দ্বারা ওভাররাইড করা হয়েছে"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"আর আনুমানিক <xliff:g id="TIME_REMAINING">%1$s</xliff:g> চলবে"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>-এ সম্পূর্ণ চার্জ হয়ে যাবে"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"অজানা"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"চার্জ হচ্ছে"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"চার্জ হচ্ছে"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"চার্জ হচ্ছে না"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"প্লাগ-ইন করা হয়েছে কিন্তু এখনই চার্জ করা যাবে না"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"পূর্ণ"</string> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index 0563abd66311..c32df18d8f14 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezano preko <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupan preko %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite za prijavu"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema internetske veze"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS serveru"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema internetske veze"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno-zeleno)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo-žuto)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Ispravka boje"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ispravka boje pomaže daltonistima da preciznije vide boje"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Zamjenjuje <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Preostalo je još oko <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napunit će se za <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"punjenje"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključen, trenutno se ne može puniti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 71b2c5d85f84..813fff4281d4 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connectat mitjançant <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible mitjançant %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca per registrar-te"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sense connexió a Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"No es pot accedir al servidor DNS privat"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexió limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sense connexió a Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermell-verd)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (blau-groc)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correcció del color"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correcció del color ajuda les persones daltòniques a veure colors més precisos"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"S\'ha substituït per <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant aproximat: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> per completar la càrrega"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconegut"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"S\'està carregant"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"s\'està carregant"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No s\'està carregant"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"El dispositiu està endollat però en aquests moments no es pot carregar"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index a381772970aa..f116733375af 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Připojeno přes <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupné prostřednictvím %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Klepnutím se zaregistrujete"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nejste připojeni k internetu"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nelze získat přístup k soukromému serveru DNS"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Omezené připojení"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nejste připojeni k internetu"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomálie (červená a zelená)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomálie (modrá a žlutá)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekce barev"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekce barev pomáhá barvoslepým lidem vidět přesnější barvy"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Přepsáno nastavením <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zbývá asi <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do nabití"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznámé"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíjí se"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nabíjení"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíjí se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Zapojeno, ale nelze nabíjet"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Nabitá"</string> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index f6e8576117d9..e3b96a4bbd03 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Forbundet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgængelig via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryk for at registrere dig"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Intet internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Der er ikke adgang til den private DNS-server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrænset forbindelse"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Intet internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopi (rød-grøn)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopi (blå-gul)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korriger farver"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Farvekorrigering gør det nemmere for farveblinde at se farver mere nøjagtigt"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tilsidesat af <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ca. <xliff:g id="TIME_REMAINING">%1$s</xliff:g> tilbage"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – opladet om <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukendt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Oplader"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"oplader"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Oplader ikke"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Enheden er tilsluttet en strømkilde. Det er ikke muligt at oplade på nuværende tidspunkt."</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fuldt"</string> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index 99a091093463..1ddabcaba02f 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Verbunden über <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Verfügbar über %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Zum Anmelden tippen"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Kein Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Auf den privaten DNS-Server kann nicht zugegriffen werden"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Eingeschränkte Verbindung"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Kein Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (Rot-Grün-Sehschwäche)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (Blau-Gelb-Sehschwäche)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Farbkorrektur"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Die Farbkorrektur hilft farbenblinden Menschen, Farben besser zu erkennen"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Außer Kraft gesetzt von \"<xliff:g id="TITLE">%1$s</xliff:g>\""</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noch etwa <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> bis zur Aufladung"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unbekannt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Wird aufgeladen"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"wird aufgeladen..."</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wird nicht geladen"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Angeschlossen, kann derzeit nicht geladen werden"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Voll"</string> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index d37ea4409124..3a97d5a43291 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Συνδέθηκε μέσω <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Διαθέσιμο μέσω %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Πατήστε για εγγραφή"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Χωρίς σύνδεση στο διαδίκτυο"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Δεν είναι δυνατή η πρόσβαση στον ιδιωτικό διακομιστή DNS."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Περιορισμένη σύνδεση"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Δεν υπάρχει σύνδεση στο διαδίκτυο"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Πρωτανοπία (κόκκινο-πράσινο)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Τριτανοπία (μπλε-κίτρινο)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Διόρθωση χρωμάτων"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Η διόρθωση χρωμάτων βοηθάει τους ανθρώπους με αχρωματοψία να βλέπουν τα χρώματα με μεγαλύτερη ακρίβεια"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Αντικαταστάθηκε από <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Απομένει/ουν περίπου <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> για την ολοκλήρωση της φόρτισης"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Άγνωστο"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Φόρτιση"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"φόρτιση"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Δεν φορτίζει"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Συνδέθηκε, δεν είναι δυνατή η φόρτιση αυτήν τη στιγμή"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Πλήρης"</string> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index 429cd3edd60b..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index 429cd3edd60b..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index 429cd3edd60b..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index 429cd3edd60b..ec0a12994d71 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Colour correction helps people with colour blindness to see more accurate colours"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge at the moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml index 1aa6cdba32fb..3b286f7c0c9e 100644 --- a/packages/SettingsLib/res/values-en-rXC/strings.xml +++ b/packages/SettingsLib/res/values-en-rXC/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"No internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Color correction"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Color correction helps people with color blindness to see more accurate colors"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> until charged"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"charging"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Plugged in, can\'t charge right now"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Full"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 7d28a3255a7b..966f2f25e827 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conexión a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Presiona para registrarte"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sin Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"No se puede acceder al servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (rojo-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarillo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección de color"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La corrección de colores ayuda a las personas con daltonismo a ver colores más exactos"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Reemplazado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante: aproximadamente <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> para completar la carga"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando."</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. No se puede cargar en este momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Cargado"</string> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index b35696f54cdc..11987221bdab 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para registrarte"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sin Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"No se ha podido acceder al servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexión limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sin Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (rojo-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarillo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección de color"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La corrección del color ayuda a las personas con daltonismo a ver los colores más reales"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>: <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tiempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> (<xliff:g id="TIME">%2$s</xliff:g> hasta que termine de cargarse)"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconocido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"No se está cargando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Se ha conectado, pero no se puede cargar en este momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index b6c112b94da7..9be72f90ad06 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ühendatud võrgu <xliff:g id="NAME">%1$s</xliff:g> kaudu"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Saadaval üksuse %1$s kaudu"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Puudutage registreerumiseks"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Interneti pole"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Privaatsele DNS-serverile ei pääse juurde"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Piiratud ühendus"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Interneti-ühendus puudub"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaalia (punane-roheline)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaalia (sinine-kollane)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värvide korrigeerimine"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Värviparanduse funktsioon aitab värvipimedatel värve täpsemini näha"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Alistas <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ligikaudu <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäänud"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täislaadimiseni"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tundmatu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laadimine"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laadimine"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei lae"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Vooluvõrgus, praegu ei saa laadida"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Täis"</string> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 4fd9add70470..d0c6c52ca7c1 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> bidez konektatuta"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s bidez erabilgarri"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Sakatu erregistratzeko"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ez dago Interneteko konexiorik"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Ezin da atzitu DNS zerbitzari pribatua"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Konexio mugatua"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ez dago Interneteko konexiorik"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanopia (gorri-berdeak)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanopia (urdin-horia)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koloreen zuzenketa"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Kolore-zuzenketak kolore zehatzagoak ikusten laguntzen die koloreentzako itsutasuna duten pertsonei."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> inguru gelditzen dira"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> guztiz kargatu arte"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ezezaguna"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Kargatzen"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"kargatzen"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ez da kargatzen ari"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Konektatuta dago. Ezin da kargatu une honetan."</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Beteta"</string> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 32a98be394cb..f15174a88395 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"متصل شده ازطریق <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"در دسترس از طریق %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"برای ثبتنام ضربه بزنید"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"عدم اتصال به اینترنت"</string> <string name="private_dns_broken" msgid="1984159464346556931">"سرور DNS خصوصی قابل دسترسی نیست"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"اتصال محدود"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"عدم دسترسی به اینترنت"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"قرمزدشواربینی (قرمز-سبز)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"آبیدشواربینی (آبی-زرد)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"تصحیح رنگ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"تصحیح رنگ به افراد مبتلا به کوررنگی کمک میکند رنگها را دقیقتر ببینند"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"توسط <xliff:g id="TITLE">%1$s</xliff:g> لغو شد"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> شارژ باقی مانده است"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> تا شارژ کامل"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ناشناس"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"در حال شارژ شدن"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"درحال شارژ شدن"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"شارژ نمیشود"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"به برق وصل شده است، درحالحاضر شارژ نمیشود"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"پر"</string> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index 84c53723d6c6..1232db3ec9f7 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Yhdistetty (<xliff:g id="NAME">%1$s</xliff:g>)"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Käytettävissä seuraavan kautta: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Rekisteröidy napauttamalla"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ei internetyhteyttä"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Ei pääsyä yksityiselle DNS-palvelimelle"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Rajallinen yhteys"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ei internetyhteyttä"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (puna-vihersokeus)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (sini-keltasokeus)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Värikorjaus"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Värinkorjaus auttaa värisokeita tulkitsemaan värejä"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Tämän ohittaa <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Noin <xliff:g id="TIME_REMAINING">%1$s</xliff:g> jäljellä"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> täyteen lataukseen"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tuntematon"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Ladataan"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ladataan"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ei laturissa"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Kytketty virtalähteeseen, lataaminen ei onnistu"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Täynnä"</string> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 995eab641719..6404df4edb00 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connecté sur le réseau <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Accessible par %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toucher pour vous connecter"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Aucune connexion Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucune connexion Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu/jaune)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction des couleurs"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correction des couleurs aide les personnes atteintes de daltonisme à mieux percevoir les couleurs"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> : <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Il reste environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> : <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Charge en cours…"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"en cours de charge"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"N\'est pas en charge"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"L\'appareil est branché, mais il ne peut pas être chargé pour le moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Pleine"</string> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 2afacab43f8d..e75063b84985 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connecté via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponible via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Appuyez ici pour vous connecter"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Aucun accès à Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Impossible d\'accéder au serveur DNS privé"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connexion limitée"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Aucun accès à Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rouge/vert)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (bleu-jaune)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correction couleur"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correction des couleurs aide les personnes atteintes de daltonisme à mieux percevoir les couleurs"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Remplacé par <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Temps restant : environ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> jusqu\'à la charge complète"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Inconnu"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Batterie en charge"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"en charge…"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Pas en charge"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Appareil branché, mais impossible de le charger pour le moment"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Pleine"</string> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index a106e60203e3..7fd32f9c4e84 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Wifi conectada a través de <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dispoñible a través de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toca para rexistrarte"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Non hai conexión a Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Non se puido acceder ao servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Pouca conexión"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Non hai conexión a Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalía (vermello-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalía (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corrección da cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A corrección das cores axuda ás persoas con daltonismo a ver as cores con maior precisión"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Anulado por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> para completar a carga"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Descoñecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Cargando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"cargando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non se está cargando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectouse, pero non se pode cargar neste momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completa"</string> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index 5f2d5cde3198..1513c57694d6 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> દ્વારા કનેક્ટ થયેલ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s દ્વારા ઉપલબ્ધ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"સાઇન અપ કરવા માટે ટૅપ કરો"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"કોઈ ઇન્ટરનેટ નથી"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ખાનગી DNS સર્વર ઍક્સેસ કરી શકાતા નથી"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"મર્યાદિત કનેક્શન"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ઇન્ટરનેટ ઍક્સેસ નથી"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"પ્રોટેનોમલી (લાલ-લીલો)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ટ્રાઇટેનોમલી(વાદળી-પીળો)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"રંગ સુધારણા"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"રંગ સુધારણા રંગ અંધત્વવાળા લોકોને વધુ સચોટ રંગો જોવામાં સહાય કરે છે"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> દ્વારા ઓવરરાઇડ થયું"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"લગભગ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> બાકી છે"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ચાર્જ થવા માટે <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"અજાણ્યું"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ચાર્જ થઈ રહ્યું છે"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ચાર્જ થઈ રહ્યું છે"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ચાર્જ થઈ રહ્યું નથી"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"પ્લગ ઇન કરેલ, હમણાં ચાર્જ કરી શકતા નથી"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"પૂર્ણ"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index d60eeadac7a6..c79c4a4090c6 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> के ज़रिए कनेक्ट किया गया"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s के द्वारा उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करने के लिए टैप करें"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इंटरनेट कनेक्शन नहीं है"</string> <string name="private_dns_broken" msgid="1984159464346556931">"निजी डीएनएस सर्वर को ऐक्सेस नहीं किया जा सकता"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित कनेक्शन"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट कनेक्शन नहीं है"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"लाल रंग पहचान न पाना (लाल-हरा)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"नीला रंग पहचान न पाना (नीला-पीला)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रंग सुधार"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रंग में सुधार करने की सेटिंग, वर्णान्धता (कलर ब्लाइंडनेस) वाले लोगों को ज़्यादा सटीक रंग देखने में मदद करती है"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> के द्वारा ओवरराइड किया गया"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"बैटरी करीब <xliff:g id="TIME_REMAINING">%1$s</xliff:g> में खत्म हो जाएगी"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> में पूरा चार्ज हो जाएगा"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हो रही है"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज हो रही है"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज नहीं हो रही है"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लग इन है, अभी चार्ज नहीं हो सकती"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"पूरी"</string> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index 28e34601444c..3ab567866c76 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezan putem mreže <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostupno putem %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dodirnite da biste se registrirali"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nema interneta"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nije moguće pristupiti privatnom DNS poslužitelju"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograničena veza"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nema interneta"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (crveno – zeleno)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (plavo – žuto)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcija boje"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcija boje pomaže slijepima za boje da vide preciznije boje"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Premošćeno postavkom <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Još otprilike <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – napunit će se za <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nepoznato"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Punjenje"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"punjenje"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ne puni se"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Uključen, trenutačno se ne može puniti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puna"</string> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index 22e03a779545..8d06f584631d 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Kapcsolódva a következőn keresztül: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Elérhető a következőn keresztül: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Koppintson a regisztrációhoz"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nincs internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"A privát DNS-kiszolgálóhoz nem lehet hozzáférni"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Korlátozott kapcsolat"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nincs internetkapcsolat"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomália (piros– zöld)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomália (kék–sárga)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Színkorrekció"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A színkorrekció segít a színtévesztőknek abban, hogy pontosabban lássák a színeket"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Felülírva erre: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Körülbelül <xliff:g id="TIME_REMAINING">%1$s</xliff:g> maradt hátra"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> a feltöltésig"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ismeretlen"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Töltés"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"töltés"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nem tölt"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Csatlakoztatva, jelenleg nem tölt"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Feltöltve"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index ecb615a40cc2..99cef2800ed6 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Միացված է <xliff:g id="NAME">%1$s</xliff:g>-ի միջոցով"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Հասանելի է %1$s-ի միջոցով"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Հպեք՝ գրանցվելու համար"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Կապ չկա"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Մասնավոր DNS սերվերն անհասանելի է"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Սահմանափակ կապ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ինտերնետ կապ չկա"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Պրոտանոմալիա (կարմիր-կանաչ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Տրիտանոմալիա (կապույտ-դեղին)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Գունաշտկում"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Գունաշտկումն օգնում է գունային դալտոնիզմ ունեցող մարդկանց ավելի ճշգրիտ տեսնել գույները"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Գերազանցված է <xliff:g id="TITLE">%1$s</xliff:g>-ից"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Լիցքը կբավարարի մոտ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> մինչև լիցքավորումը"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Անհայտ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Լիցքավորում"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"լիցքավորում"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Չի լիցքավորվում"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Միացված է հոսանքի աղբյուրին, սակայն այս պահին չի կարող լիցքավորվել"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Լիցքավորված է"</string> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index cfbda04f176d..25de382b755a 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tersambung melalui <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketuk untuk mendaftar"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Tidak ada internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Server DNS pribadi tidak dapat diakses"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Koneksi terbatas"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tidak ada internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (merah-hijau)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (biru-kuning)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Koreksi warna"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Koreksi warna membantu orang penderita buta warna untuk melihat warna yang lebih akurat"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Digantikan oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Sekitar <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> lagi terisi penuh"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengisi daya"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"mengisi daya baterai"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengisi daya"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Tercolok, tidak dapat mengisi baterai sekarang"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Penuh"</string> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index ba4c00976ccb..8fe382a698d4 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tenging í gegnum <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Í boði í gegnum %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ýttu til að skrá þig"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Engin nettenging"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Ekki næst í DNS-einkaþjón"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Takmörkuð tenging"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Engin nettenging"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Litblinda (rauðgræn)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Litblinda (blágul)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Litaleiðrétting"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Litaleiðrétting hjálpar fólki með litblindu að sjá réttari liti"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Hnekkt af <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Um það bil <xliff:g id="TIME_REMAINING">%1$s</xliff:g> eftir"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> að fullri hleðslu"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Óþekkt"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Í hleðslu"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"í hleðslu"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ekki í hleðslu"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Í sambandi, ekki hægt að hlaða eins og er"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fullhlaðin"</string> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index 50e5777ed9d9..c9abc74fce07 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Connesso tramite <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibile tramite %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tocca per registrarti"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Internet assente"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Non è possibile accedere al server DNS privato"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Connessione limitata"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nessuna connessione a Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalìa (rosso-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalìa (blu-giallo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correzione del colore"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"La correzione del colore consente alle persone daltoniche di vedere colori più accurati"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valore sostituito da <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo rimanente: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> circa"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> alla carica completa"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Sconosciuta"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"In carica"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"in carica"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Non in carica"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Collegato alla corrente. Impossibile caricare al momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Carica"</string> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 9f1e457383e1..1921d032775f 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"מחוברת באמצעות <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"זמינה דרך %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"יש להקיש כדי להירשם"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"אין אינטרנט"</string> <string name="private_dns_broken" msgid="1984159464346556931">"לא ניתן לגשת לשרת DNS הפרטי"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"חיבור מוגבל"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"אין אינטרנט"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"פרוטנומליה (אדום-ירוק)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"טריטנומליה (כחול-צהוב)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"תיקון צבע"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"תיקון צבע עוזר למשתמשים עם עיוורון צבעים לראות צבעים מדויקים יותר"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"נעקף על ידי <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"הזמן הנותר: בערך <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> עד הטעינה"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"לא ידוע"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"בטעינה"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"בטעינה"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"לא בטעינה"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"המכשיר מחובר, אבל לא ניתן לטעון עכשיו"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"מלאה"</string> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index 47020ed45794..e4e1ab91b770 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> で接続しました"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s経由で使用可能"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"タップして登録してください"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"インターネットに接続されていません"</string> <string name="private_dns_broken" msgid="1984159464346556931">"プライベート DNS サーバーにアクセスできません"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"接続が制限されています"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"インターネット未接続"</string> @@ -204,7 +203,7 @@ <string name="vpn_settings_not_available" msgid="2894137119965668920">"このユーザーはVPN設定を利用できません。"</string> <string name="tethering_settings_not_available" msgid="266821736434699780">"このユーザーはテザリング設定を利用できません"</string> <string name="apn_settings_not_available" msgid="1147111671403342300">"このユーザーはアクセスポイント名設定を利用できません"</string> - <string name="enable_adb" msgid="8072776357237289039">"USBデバッグ"</string> + <string name="enable_adb" msgid="8072776357237289039">"USB デバッグ"</string> <string name="enable_adb_summary" msgid="3711526030096574316">"USB接続時はデバッグモードにする"</string> <string name="clear_adb_keys" msgid="3010148733140369917">"USBデバッグの許可の取り消し"</string> <string name="bugreport_in_power" msgid="8664089072534638709">"バグレポートのショートカット"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"第一色弱(赤緑)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"第三色弱(青黄)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色補正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色補正機能を利用すると、色覚異常のユーザーがより正確に色を判別できるようになります"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g>によって上書き済み"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 充電完了まで <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"充電しています"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"充電していません"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"接続されていますが、現在、充電できません"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"フル"</string> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index 116488d1c151..b2cfb17cdd43 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"დაკავშირებულია <xliff:g id="NAME">%1$s</xliff:g>-ით"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"ხელმისაწვდომია %1$s-ით"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"შეეხეთ რეგისტრაციისთვის"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ინტერნეტ-კავშირი არ არის"</string> <string name="private_dns_broken" msgid="1984159464346556931">"პირად DNS სერვერზე წვდომა შეუძლებელია"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"შეზღუდული კავშირი"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ინტერნეტ-კავშირი არ არის"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"პროტოანომალია (წითელი-მწვანე)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ტრიტანომალია (ლურჯი-ყვითელი)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ფერის კორექცია"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ფერის კორექცია ეხმარება ფერითი სიბრმავის მქონე ადამიანებს, უფრო ზუსტად გაარჩიონ ფერები"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"უკუგებულია <xliff:g id="TITLE">%1$s</xliff:g>-ის მიერ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"დარჩა დაახლოებით <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> დატენვამდე"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"უცნობი"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"იტენება"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"იტენება"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"არ იტენება"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"მიერთებულია, დატენვა ამჟამად ვერ ხერხდება"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ბატარეა დატენილია"</string> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index 3c4fdb739ef5..756bc06b630e 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> арқылы жалғанған"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s арқылы қолжетімді"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Тіркелу үшін түртіңіз."</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернетпен байланыс жоқ."</string> <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS серверіне кіру мүмкін емес."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Шектеулі байланыс"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернетпен байланыс жоқ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (қызыл-жасыл)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (көк-сары)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Түсті түзету"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Түсті түзету түсті ажырата алмайтын адамдарға оларды дәлірек көруге көмектеседі"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> үстінен басқан"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Шамамен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> қалды"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – зарядталғанға дейін <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгісіз"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Зарядталуда"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"зарядталуда"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Зарядталу орындалып жатқан жоқ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Қосылған, зарядталмайды"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Толы"</string> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index 87a4f19f3d67..cdf92b38a46c 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"ភ្ជាប់តាម <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"មានតាមរយៈ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ចុចដើម្បីចុះឈ្មោះ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"គ្មានអ៊ីនធឺណិតទេ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"មិនអាចចូលប្រើម៉ាស៊ីនមេ DNS ឯកជនបានទេ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ការតភ្ជាប់មានកម្រិត"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"គ្មានអ៊ីនធឺណិតទេ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ក្រហមពណ៌បៃតង)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ពណ៌ខៀវ-លឿង)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ការកែពណ៌"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ការកែតម្រូវពណ៌ជួយដល់អ្នកដែលមិនអាចបែងចែកពណ៌ឱ្យមើលឃើញពណ៌ដែលត្រឹមត្រូវជាមុន"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"បដិសេធដោយ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"នៅសល់ប្រហែល <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ទៀត"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ទៀតទើបត្រូវសាក"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"មិនស្គាល់"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"កំពុងបញ្ចូលថ្ម"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"កំពុងសាកថ្ម"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"មិនកំពុងបញ្ចូលថ្ម"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ដោតសាកថ្មរួចហើយ ប៉ុន្តែសាកថ្មមិនចូលទេឥឡូវនេះ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ពេញ"</string> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index 144dddb1f7ff..4a24691f9606 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ಆ್ಯಪ್ ಮೂಲಕ ಸಂಪರ್ಕಿಸಲಾಗಿದೆ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ಮೂಲಕ ಲಭ್ಯವಿದೆ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ಸೈನ್ ಅಪ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ಖಾಸಗಿ DNS ಸರ್ವರ್ ಅನ್ನು ಪ್ರವೇಶಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ಸೀಮಿತ ಸಂಪರ್ಕ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ಇಂಟರ್ನೆಟ್ ಇಲ್ಲ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ಪ್ರೊಟನೋಮಲಿ (ಕೆಂಪು-ಹಸಿರು)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ಟ್ರಿಟನೋಮಲಿ (ನೀಲಿ-ಹಳದಿ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ವರ್ಣ ಅಂಧತ್ವ ಹೊಂದಿರುವ ಜನರಿಗೆ ಬಣ್ಣಗಳನ್ನು ಹೆಚ್ಚು ನಿಖರವಾಗಿ ವೀಕ್ಷಿಸಲು ಬಣ್ಣ ತಿದ್ದುಪಡಿ ಸಹಾಯ ಮಾಡುತ್ತದೆ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ಮೂಲಕ ಅತಿಕ್ರಮಿಸುತ್ತದೆ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ಚಾರ್ಜ್ ಆಗಲು <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯ ಬೇಕು"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ಅಪರಿಚಿತ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ಚಾರ್ಜ್ ಆಗುತ್ತಿಲ್ಲ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ಪ್ಲಗ್ ಇನ್ ಮಾಡಲಾಗಿದೆ, ಇದೀಗ ಚಾರ್ಜ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ಭರ್ತಿ"</string> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index df9f21d800ef..51896e96f856 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>을(를) 통해 연결됨"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s을(를) 통해 사용 가능"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"탭하여 가입"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"인터넷 연결 없음"</string> <string name="private_dns_broken" msgid="1984159464346556931">"비공개 DNS 서버에 액세스할 수 없습니다."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"제한된 연결"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"인터넷 연결 없음"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"적색약(적녹)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"청색약(청황)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"색보정"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"색상 보정을 사용하면 색맹인 사용자가 색상을 정확하게 구분하는 데 도움이 됩니다."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> 우선 적용됨"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>, <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"남은 시간 약 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 충전 완료까지 <xliff:g id="TIME">%2$s</xliff:g> 남음"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"알 수 없음"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"충전 중"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"충전 중"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"충전 안함"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"전원이 연결되었지만 현재 충전할 수 없음"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"충전 완료"</string> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index 13c414404da2..061f6fd37c6b 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> аркылуу туташты"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s аркылуу жеткиликтүү"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Катталуу үчүн таптап коюңуз"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернет жок"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Жеке DNS сервери жеткиликсиз"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Байланыш чектелген"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернет жок"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (кызыл-жашыл)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (көк-сары)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Түсүн тууралоо"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Түстү тууралоо жөндөөсү түстөрдү айырмалап көрбөгөн адамдарга түстөрдү тагыраак билүүгө жардам берет"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> менен алмаштырылган"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Болжол менен <xliff:g id="TIME_REMAINING">%1$s</xliff:g> калды"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> кийин кубатталат"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Белгисиз"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Кубатталууда"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"кубатталууда"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Кубат алган жок"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Сайылып турат, учурда кубаттоо мүмкүн эмес"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Толук"</string> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index 48224f4c2f81..bfbd8a9a673e 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"ເຊື່ອມຕໍ່ຜ່ານ <xliff:g id="NAME">%1$s</xliff:g> ແລ້ວ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"ມີໃຫ້ຜ່ານ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ແຕະເພື່ອສະໝັກ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ບໍ່ມີອິນເຕີເນັດ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີ DNS ສ່ວນຕົວໄດ້"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ການເຊື່ອມຕໍ່ຈຳກັດ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ບໍ່ມີອິນເຕີເນັດ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ສີແດງ-ສີຂຽວ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ສີຟ້າ-ສີເຫຼືອງ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ການປັບແຕ່ງສີ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ການແກ້ໄຂສີຈະຊ່ວຍໃຫ້ຄົນທີ່ຕາບອດສີເຫັນສີຕ່າງໆໄດ້ຖືກຕ້ອງຍິ່ງຂຶ້ນ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"ຖືກແທນໂດຍ <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ຈົນກວ່າຈະສາກເຕັມ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ບໍ່ຮູ້ຈັກ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ກຳລັງສາກໄຟ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ກຳລັງສາກໄຟ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ບໍ່ໄດ້ສາກໄຟ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ສຽບສາຍແລ້ວ, ບໍ່ສາມາດສາກໄດ້ໃນຕອນນີ້"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ເຕັມ"</string> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index d080334062ad..76d06faec154 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Prisijungta naudojant programą „<xliff:g id="NAME">%1$s</xliff:g>“"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Pasiekiama naudojant „%1$s“"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Palieskite, kad prisiregistruotumėte"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nėra interneto ryšio"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Privataus DNS serverio negalima pasiekti"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ribotas ryšys"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nėra interneto ryšio"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (raudona, žalia)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (mėlyna, geltona)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Spalvų taisymas"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Naudojant spalvų taisymo funkciją daltonikai gali geriau matyti spalvas"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nepaisyta naudojant nuostatą „<xliff:g id="TITLE">%1$s</xliff:g>“"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Liko maždaug <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – iki visiškos įkrovos liko <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nežinomas"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Kraunasi..."</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"įkraunama"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nekraunama"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Įjungta į maitinimo lizdą, bet šiuo metu įkrauti neįmanoma"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Visiškai įkrautas"</string> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index e13a50d990a7..e4652d1a44eb 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Savienojums ar <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Pieejams, izmantojot %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Pieskarieties, lai reģistrētos"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nav interneta"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Nevar piekļūt privātam DNS serverim."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ierobežots savienojums"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nav piekļuves internetam"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomālija (sarkans/zaļš)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomālija (zils/dzeltens)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Krāsu korekcija"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Krāsu korekcija palīdz cilvēkiem ar krāsu aklumu redzēt precīzākas krāsas."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Jaunā preference: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> — <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Aptuvenais atlikušais laiks: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="TIME">%2$s</xliff:g> līdz pilnai uzlādei"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nezināms"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Uzlāde"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"notiek uzlāde"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenotiek uzlāde"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Pievienots, taču pašlaik nevar veikt uzlādi"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Pilns"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index d027ffe4f44f..21b2f965d302 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Поврзано преку <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Достапно преку %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Допрете за да се регистрирате"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нема интернет"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Не може да се пристапи до приватниот DNS-сервер"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена врска"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернет"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалија (слепило за црвена и зелена)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалија (слепило за сина и жолта)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекција на бои"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекцијата на бои им помага на луѓето со далтонизам попрецизно да ги гледаат боите"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Прескокнато според <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Уште околу <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> до целосно полнење"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Се полни"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"се полни"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не се полни"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Приклучен е, но батеријата не може да се полни во моментов"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Полна"</string> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index e2a3d9fbd4ba..85aa000fc7f6 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> മുഖേന കണക്റ്റ് ചെയ്തു"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s വഴി ലഭ്യം"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"സൈൻ അപ്പ് ചെയ്യാൻ ടാപ്പ് ചെയ്യുക"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ഇന്റർനെറ്റ് ഇല്ല"</string> <string name="private_dns_broken" msgid="1984159464346556931">"സ്വകാര്യ DNS സെർവർ ആക്സസ് ചെയ്യാനാവില്ല"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"പരിമിത കണക്ഷൻ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ഇന്റർനെറ്റ് ഇല്ല"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"പ്രോട്ടാനോമലി (ചുവപ്പ്-പച്ച)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ട്രിട്ടാനോമലി (നീല-മഞ്ഞ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"വർണ്ണം ക്രമീകരിക്കൽ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"വർണ്ണം ശരിയാക്കൽ, വർണ്ണാന്ധത ബാധിച്ച ആളുകൾക്ക് നിറങ്ങൾ കൂടുതൽ കൃത്യമായി കാണാൻ സഹായിക്കുന്നു"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ഉപയോഗിച്ച് അസാധുവാക്കി"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ഏതാണ്ട് <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ശേഷിക്കുന്നു"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - പൂർണ്ണമായി ചാർജാവാൻ <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"അജ്ഞാതം"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ചാർജ് ചെയ്യുന്നു"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ചാർജ് ചെയ്യുന്നു"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ചാർജ്ജുചെയ്യുന്നില്ല"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"പ്ലഗ് ഇൻ ചെയ്തു, ഇപ്പോൾ ചാർജ് ചെയ്യാനാവില്ല"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"നിറഞ്ഞു"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index dd08c9e9fdfc..232148a490ba 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g>-р холбогдсон"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s-р боломжтой"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Бүртгүүлэхийн тулд товшино уу"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Интернэт алга"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Хувийн DNS серверт хандах боломжгүй байна"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Хязгаарлагдмал холболт"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Интернэт алга"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномаль (улаан-ногоон)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомаль (цэнхэр-шар)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Өнгө тохируулах"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Өнгөний залруулга нь өнгөний сохортой хүмүүст илүү оновчтой өнгө харахад тусалдаг"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Давхарласан <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Ойролцоогоор <xliff:g id="TIME_REMAINING">%1$s</xliff:g> үлдсэн"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - цэнэглэх хүртэл <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Тодорхойгүй"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Цэнэглэж байна"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"цэнэглэж байна"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Цэнэглэхгүй байна"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Залгаастай тул одоо цэнэглэх боломжгүй"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Дүүрэн"</string> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index fb7cd1f3cf31..8767b4f145b4 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे कनेक्ट केले"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s द्वारे उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप करण्यासाठी टॅप करा"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इंटरनेट नाही"</string> <string name="private_dns_broken" msgid="1984159464346556931">"खाजगी DNS सर्व्हर ॲक्सेस करू शकत नाही"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"मर्यादित कनेक्शन"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इंटरनेट नाही"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"क्षीण रक्तवर्णांधता (लाल-हिरवा)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"रंग दृष्टी कमतरता (निळा-पिवळा)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रंग सुधारणा"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रंग सुधारणा ही वर्णांधता असलेल्या लोकांना रंग अधिक अचूक दिसण्यात मदत करते"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारे अधिलिखित"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"अंदाजे <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाकी आहे"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> पर्यंत पूर्ण चार्ज होईल"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज होत आहे"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज होत आहे"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज होत नाही"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लग इन केलेले आहे, आता चार्ज करू शकत नाही"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"पूर्ण"</string> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index 72b15ff2a172..b79b80130d90 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Disambungkan melalui <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tersedia melalui %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Ketik untuk daftar"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Tiada Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Pelayan DNS peribadi tidak boleh diakses"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Sambungan terhad"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Tiada Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (merah-hijau)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (biru-kuning)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Pembetulan warna"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Pembetulan warna membantu orang yang mengalami kebutaan warna melihat warna yang lebih tepat"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Diatasi oleh <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Kira-kira <xliff:g id="TIME_REMAINING">%1$s</xliff:g> lagi"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> sehingga dicas"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Tidak diketahui"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Mengecas"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"mengecas"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Tidak mengecas"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Dipalamkan, tidak boleh mengecas sekarang"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Penuh"</string> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index 24017ecd8196..b2ad66418d53 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> မှတစ်ဆင့် ချိတ်ဆက်ထားသည်"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s မှတစ်ဆင့်ရနိုင်သည်"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"အကောင့်ဖွင့်ရန် တို့ပါ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"အင်တာနက် မရှိပါ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"သီးသန့် ဒီအန်အက်စ် (DNS) ဆာဗာကို သုံး၍မရပါ။"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ချိတ်ဆက်မှု ကန့်သတ်ထားသည်"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"အင်တာနက် မရှိပါ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (အနီ-အစိမ်း)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (အပြာ-အဝါ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"အရောင်ပြင်ဆင်မှု"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"အရောင်ပြင်ဆင်ခြင်းက အရောင်ကန်းသူများအတွက် ပိုမိုမှန်ကန်သော အရောင်များဖြင့် ကြည့်နိုင်ရန် ကူညီမည်"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> မှ ကျော်၍ လုပ်ထားသည်။"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ခန့် ကျန်သည်"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - အားပြည့်ရန် <xliff:g id="TIME">%2$s</xliff:g> ကျန်သည်"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"မသိ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"အားသွင်းနေပါသည်"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"အားသွင်းနေပါသည်"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"အားသွင်းမနေပါ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ပလပ်ထိုးထားသောကြောင့် ယခုအားသွင်း၍ မရသေးပါ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"အပြည့်"</string> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index aca13a78c453..6a1f158b50c7 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Tilkoblet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tilgjengelig via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Trykk for å registrere deg"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ingen internettilkobling"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Den private DNS-tjeneren kan ikke nås"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begrenset tilkobling"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ingen internettilkobling"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (rød-grønn)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (blå-gul)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Fargekorrigering"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Med fargekorrigering kan personer med fargeblindhet se mer nøyaktige farger"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overstyres av <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Omtrent <xliff:g id="TIME_REMAINING">%1$s</xliff:g> gjenstår"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> til batteriet er fulladet"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Ukjent"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Lader"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"lader"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Lader ikke"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Laderen er koblet til – kan ikke lade akkurat nå"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fullt"</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index b2c7518eb8d6..9cd5e2079e9e 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> मार्फत जडान गरिएको"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s मार्फत उपलब्ध"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"साइन अप गर्न ट्याप गर्नुहोस्"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"इन्टरनेट छैन"</string> <string name="private_dns_broken" msgid="1984159464346556931">"निजी DNS सर्भरमाथि पहुँच प्राप्त गर्न सकिँदैन"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"सीमित जडान"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"इन्टरनेटमाथिको पहुँच छैन"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"प्रोटानेमली (रातो, हरियो)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ट्रिटानोमेली (निलो-पंहेलो)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"रङ्ग सुधार"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"रङ सुधार गर्नुले रङ छुट्याउन नसक्ने मान्छेलाई थप सही रङ देख्न मद्दत गर्दछ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> द्वारा अधिरोहित"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"लगभग <xliff:g id="TIME_REMAINING">%1$s</xliff:g> बाँकी छ"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"पूर्ण चार्ज हुन <xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> लाग्छ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"अज्ञात"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"चार्ज हुँदै"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"चार्ज हुँदै"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"चार्ज भइरहेको छैन"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"प्लगइन गरिएको छ, अहिले नै चार्ज गर्न सकिँदैन"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"पूर्ण चार्ज भएको स्थिति"</string> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index c74683bdf238..8d5dea4e37d4 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Verbonden via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Beschikbaar via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tik om aan te melden"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Geen internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Geen toegang tot privé-DNS-server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Beperkte verbinding"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Geen internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (rood-groen)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (blauw-geel)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Kleurcorrectie"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Met behulp van kleurcorrectie kunnen mensen die kleurenblind zijn, nauwkeurigere kleuren te zien krijgen"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overschreven door <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Nog ongeveer <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> tot opgeladen"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Onbekend"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Opladen"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"opladen"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Wordt niet opgeladen"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Aangesloten, kan nu niet opladen"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Volledig"</string> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index 704fc42e7ddf..c0489c1dbc8f 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ଦ୍ବାରା ସଂଯୋଗ କରାଯାଇଛି"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ମାଧ୍ୟମରେ ଉପଲବ୍ଧ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ସାଇନ୍ ଅପ୍ ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ବ୍ୟକ୍ତିଗତ DNS ସର୍ଭର୍ ଆକ୍ସେସ୍ କରିହେବ ନାହିଁ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ସୀମିତ ସଂଯୋଗ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"କୌଣସି ଇଣ୍ଟରନେଟ୍ ନାହିଁ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ପ୍ରୋଟାନୋମାଲି (ଲାଲ୍-ସବୁଜ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ନୀଳ-ହଳଦିଆ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ରଙ୍ଗ ସଠିକତା"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"କଲର୍ କରେକ୍ସନ୍ ରଙ୍ଗ ଚିହ୍ନିବାରେ ସମସ୍ୟା ଥିବା ଲୋକମାନଙ୍କୁ ଅଧିକ ସଠିକ୍ ରଙ୍ଗ ଦେଖିବାରେ ସାହାଯ୍ୟ କରିଥାଏ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ଦ୍ୱାରା ଓଭର୍ରାଇଡ୍ କରାଯାଇଛି"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ପାଖାପାଖି <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ବଳକା ଅଛି"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ଚାର୍ଜ ହେବା ପର୍ଯ୍ୟନ୍ତ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ଅଜ୍ଞାତ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ଚାର୍ଜ ହେଉଛି"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ଚାର୍ଜ ହେଉଛି"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ଚାର୍ଜ ହେଉନାହିଁ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ପ୍ଲଗ୍ରେ ଲାଗିଛି, ହେଲେ ଏବେ ଚାର୍ଜ କରିପାରିବ ନାହିଁ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ଚାର୍ଜ ସମ୍ପୂର୍ଣ୍ଣ"</string> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index bf5a32cd33b5..7355418c4d13 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ਰਾਹੀਂ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ਰਾਹੀਂ ਉਪਲਬਧ"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ਸਾਈਨ-ਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ਨਿੱਜੀ ਡੋਮੇਨ ਨਾਮ ਪ੍ਰਣਾਲੀ (DNS) ਸਰਵਰ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"ਸੀਮਤ ਕਨੈਕਸ਼ਨ"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ਇੰਟਰਨੈੱਟ ਨਹੀਂ"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (ਲਾਲ-ਹਰਾ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (ਨੀਲਾ-ਪੀਲਾ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"ਰੰਗ ਸੁਧਾਈ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"ਰੰਗ ਸੁਧਾਈ ਨਾਲ ਰੰਗਾਂ ਦੇ ਅੰਨ੍ਹਾਪਣ ਦੇ ਸ਼ਿਕਾਰ ਲੋਕਾਂ ਦੀ ਵਧੇਰੇ ਸਟੀਕ ਰੰਗਾਂ ਨੂੰ ਦੇਖਣ ਵਿੱਚ ਮਦਦ ਕੀਤੀ ਜਾਂਦੀ ਹੈ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ਦੁਆਰਾ ਓਵਰਰਾਈਡ ਕੀਤਾ"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"ਲਗਭਗ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ਬਾਕੀ"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ਤੱਕ ਚਾਰਜ ਹੋ ਜਾਵੇਗੀ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ਅਗਿਆਤ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ਚਾਰਜ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ਚਾਰਜ ਨਹੀਂ ਹੋ ਰਿਹਾ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ਪਲੱਗ ਲੱਗਾ ਹੋਇਆ ਹੈ, ਇਸ ਸਮੇਂ ਚਾਰਜ ਨਹੀਂ ਹੋ ਸਕਦੀ"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"ਪੂਰੀ"</string> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index d907760c5567..a3ccb07a74a1 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Połączenie przez: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Dostępne przez %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Kliknij, by się zarejestrować"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Brak internetu"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Brak dostępu do prywatnego serwera DNS"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ograniczone połączenie"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brak internetu"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (czerwony-zielony)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (niebieski-żółty)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korekcja kolorów"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcja kolorów pomaga osobom z zaburzeniami rozpoznawania barw lepiej je widzieć."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Nadpisana przez <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Jeszcze około <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – do naładowania <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Nieznane"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Ładowanie"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ładowanie"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nie podłączony"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Podłączony. Nie można teraz ładować"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Naładowana"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index e61c84e01179..f4182bfd7896 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção de cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cores ajuda pessoas com daltonismo a ver cores de forma mais precisa"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a carga completa"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"carregando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. Não é possível carregar no momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Carregada"</string> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 8ceeb6333210..2f206d472659 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ligado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível através de %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível aceder ao servidor DNS."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ligação limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção da cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cor ajuda as pessoas com daltonismo a ver cores mais precisas."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Resta(m) cerca de <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> até ficar carregada"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"A carregar"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"a carregar…"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está a carregar"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ligada à corrente, não é possível carregar neste momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Completo"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index e61c84e01179..f4182bfd7896 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectado via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponível via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Toque para se inscrever"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Sem Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Não é possível acessar o servidor DNS privado"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexão limitada"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Sem Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalia (vermelho-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalia (azul-amarelo)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Correção de cor"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"A correção de cores ajuda pessoas com daltonismo a ver cores de forma mais precisa"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Substituído por <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Tempo restante aproximado: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g>: <xliff:g id="TIME">%2$s</xliff:g> até a carga completa"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Desconhecido"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Carregando"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"carregando"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Não está carregando"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectado. Não é possível carregar no momento"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Carregada"</string> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 5ad8dfd136de..6e8bbd14e649 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Conectat prin <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Disponibilă prin %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Atingeți pentru a vă înscrie"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Fără conexiune la internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Serverul DNS privat nu poate fi accesat"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Conexiune limitată"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Fără conexiune la internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalie (roșu-verde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalie (albastru-galben)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Corecția culorii"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Corecția culorii ajută persoanele cu daltonism să vadă culori mai exacte"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Valoare înlocuită de <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Timp aproximativ rămas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> până la încărcare"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Necunoscut"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Se încarcă"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"se încarcă"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nu se încarcă"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Conectat, nu se poate încărca chiar acum"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Complet"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 104efdc57b45..87831c76dc85 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Подключено через приложение \"<xliff:g id="NAME">%1$s</xliff:g>\"."</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступно через %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Нажмите, чтобы зарегистрироваться"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нет подключения к Интернету"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Доступа к частному DNS-серверу нет."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Подключение к сети ограничено."</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нет подключения к Интернету"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалия (красный/зеленый)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалия (синий/желтый)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Коррекция цвета"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Коррекция цвета помогает пользователям с нарушениями цветового зрения лучше различать изображение на экране."</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Новая настройка: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"Уровень заряда – <xliff:g id="PERCENTAGE">%1$s</xliff:g>. <xliff:g id="TIME_STRING">%2$s</xliff:g>."</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Заряда хватит примерно на <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до полной зарядки"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Неизвестно"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Идет зарядка"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"заряжается"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряжается"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Подключено, не заряжается"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Батарея заряжена"</string> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index f5dad8748b24..f0a823a01ead 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> හරහා සම්බන්ධයි"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s හරහා ලබා ගැනීමට හැකිය"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"ලියාපදිංචි වීමට තට්ටු කරන්න"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"අන්තර්ජාලය නැත"</string> <string name="private_dns_broken" msgid="1984159464346556931">"පුද්ගලික DNS සේවාදායකයට ප්රවේශ වීමට නොහැකිය"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"සීමිත සම්බන්ධතාව"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"අන්තර්ජාලය නැත"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"වර්ණ දුර්වලතාවය (රතු-කොළ)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"වර්ණ අන්ධතාවය (නිල්-කහ)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"වර්ණ නිවැරදි කිරීම"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"වර්ණ අන්ධතාවෙන් පෙළෙන පුද්ගලයන්ට වඩාත් නිරවද්ය වර්ණ බැලීමට වර්ණ නිවැරදි කිරීම සහාය වේ"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> මගින් ඉක්මවන ලදී"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> ක් පමණ ඉතිරියි"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ආරෝපණය වන තෙක් <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"නොදනී"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ආරෝපණය වෙමින්"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ආරෝපණය වේ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ආරෝපණය නොවේ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"පේනුගත කර ඇත, මේ අවස්ථාවේදී ආරෝපණය කළ නොහැකිය"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"පූර්ණ"</string> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index 527dafb95a2e..fc2ba1dee469 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Pripojené prostredníctvom siete <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"K dispozícii prostredníctvom %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Prihláste sa klepnutím"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Bez internetu"</string> <string name="private_dns_broken" msgid="1984159464346556931">"K súkromnému serveru DNS sa nepodarilo získať prístup"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Obmedzené pripojenie"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Žiadny internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomália (červená a zelená)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomália (modrá a žltá)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Úprava farieb"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korekcia farieb pomáha farboslepým ľuďom vidieť presnejšie farby"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Prekonané predvoľbou <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznáme"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nabíja sa"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nabíja sa"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nenabíja sa"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Pripojené, ale nie je možné nabíjať"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Nabitá"</string> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 481695046625..cda2e067fdc2 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Povezava vzpostavljena prek omrežja <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Na voljo prek: %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Dotaknite se, če se želite registrirati"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ni internetne povezave"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Do zasebnega strežnika DNS ni mogoče dostopati"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Omejena povezava"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Brez internetne povezave"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomalija (rdeča – zelena)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomalija (modra – rumena)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Popravljanje barv"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Popravljanje barv osebam z barvno slepoto pomaga, da vidijo bolj prave barve"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Preglasila nastavitev: <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Še približno <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do polne napolnjenosti"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Neznano"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Polnjenje"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"polnjenje"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Se ne polni"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Priključeno, trenutno ni mogoče polniti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Poln"</string> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index 12511dd3582c..9854017a6a25 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Lidhur përmes <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"E mundshme përmes %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Trokit për t\'u regjistruar"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Nuk ka internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Serveri privat DNS nuk mund të qaset"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Lidhje e kufizuar"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Nuk ka internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (e kuqe - e gjelbër)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (e kaltër - e verdhë)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Korrigjimi i ngjyrës"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Korrigjimi i ngjyrës i ndihmon njerëzit me daltonizëm të shohin ngjyra më të sakta"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Mbivendosur nga <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Rreth <xliff:g id="TIME_REMAINING">%1$s</xliff:g> të mbetura"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> deri sa të karikohen"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"I panjohur"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Po karikohet"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"po karikohet"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Nuk po karikohet"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Në prizë, por nuk mund të ngarkohet për momentin"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"E mbushur"</string> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 4724be52f87c..b59a568d1685 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Повезано преко: <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступна је преко приступне тачке %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Додирните да бисте се регистровали"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Нема интернета"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Приступ приватном DNS серверу није успео"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Ограничена веза"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Нема интернета"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалија (црвено-зелено)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалија (плаво-жуто)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекција боја"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекција боја помаже људима који су далтонисти да прецизније виде боје"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замењује га <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g>–<xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Преостало је око <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – напуниће се за <xliff:g id="TIME">%2$s</xliff:g>"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Непознато"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Пуни се"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"пуни се"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не пуни се"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Прикључено је, али пуњење тренутно није могуће"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Пуна"</string> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index 9d1bc42b613a..f9ed6d43a310 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Anslutet via <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Tillgängligt via %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Tryck för att logga in"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Inget internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Det går inte att komma åt den privata DNS-servern."</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Begränsad anslutning"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Inget internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomali (rött-grönt)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomali (blått-gult)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Färgkorrigering"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Med färgkorrigering kan färgblinda personer se mer korrekta färger"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Har åsidosatts av <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cirka <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kvar"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> till full laddning"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Okänd"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Laddar"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"laddas"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Laddar inte"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Inkopplad, kan inte laddas just nu"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Fullt"</string> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index 1e2489f89ff6..40e025a9b61f 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Imeunganishwa kupitia <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Inapatikana kupitia %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Gusa ili ujisajili"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Hakuna intaneti"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Seva ya faragha ya DNS haiwezi kufikiwa"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Muunganisho hafifu"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Hakuna intaneti"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (nyekundu-kijani)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (samawati-manjano)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Usahihishaji wa rangi"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Urekebishaji wa rangi huwasaidia watu wenye matatizo ya kutofautisha rangi ili waone rangi nyingi sahihi"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Imetanguliwa na <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Zimesalia takribani <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hadi ijae chaji"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Haijulikani"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Inachaji"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"inachaji"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Haichaji"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Haiwezi kuchaji kwa sasa"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Imejaa"</string> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index fcb801b112a0..36a92ed1a706 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> மூலம் இணைக்கப்பட்டது"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s வழியாகக் கிடைக்கிறது"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"பதிவு செய்யத் தட்டவும்"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"இணைய இணைப்பு இல்லை"</string> <string name="private_dns_broken" msgid="1984159464346556931">"தனிப்பட்ட DNS சேவையகத்தை அணுக இயலாது"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"வரம்பிற்கு உட்பட்ட இணைப்பு"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"இணைய இணைப்பு இல்லை"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"நிறம் அடையாளங்காண முடியாமை (நீலம்-மஞ்சள்)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"வண்ணத்திருத்தம்"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"வண்ணத் திருத்தத்தால் நிறக்குருடு உள்ளவர்களால் வண்ணங்களை இன்னும் துல்லியமாகப் பார்க்க முடியும்"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> மூலம் மேலெழுதப்பட்டது"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"கிட்டத்தட்ட <xliff:g id="TIME_REMAINING">%1$s</xliff:g> மீதமுள்ளது"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழு சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"அறியப்படாத"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"சார்ஜ் ஆகிறது"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"சார்ஜ் ஆகிறது"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"சார்ஜ் செய்யப்படவில்லை"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"செருகப்பட்டது, ஆனால் இப்போது சார்ஜ் செய்ய முடியவில்லை"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"முழுவதும் சார்ஜ் ஆனது"</string> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 7a8045111baa..5ea380ccdf58 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ద్వారా కనెక్ట్ చేయబడింది"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s ద్వారా అందుబాటులో ఉంది"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"సైన్ అప్ చేయడానికి నొక్కండి"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ఇంటర్నెట్ లేదు"</string> <string name="private_dns_broken" msgid="1984159464346556931">"ప్రైవేట్ DNS సర్వర్ను యాక్సెస్ చేయడం సాధ్యపడదు"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"పరిమిత కనెక్షన్"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ఇంటర్నెట్ లేదు"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ప్రొటానోమలీ (ఎరుపు-ఆకుపచ్చ రంగు)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ట్రైటనోమలీ (నీలం-పసుపు రంగు)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"రంగు సవరణ"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"రంగు సవరణ అనేది వర్ణాంధత్వం ఉన్న వ్యక్తులకు మరింత ఖచ్చితమైన రంగులను చూడడానికి సహాయపడుతుంది"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> ద్వారా భర్తీ చేయబడింది"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"<xliff:g id="TIME_REMAINING">%1$s</xliff:g> సమయం మిగిలి ఉంది"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - ఛార్జ్ అవ్వడానికి <xliff:g id="TIME">%2$s</xliff:g> పడుతుంది"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"తెలియదు"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"ఛార్జ్ అవుతోంది"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"ఛార్జ్ అవుతోంది"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ఛార్జ్ కావడం లేదు"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"ప్లగ్ ఇన్ చేయబడింది, ప్రస్తుతం ఛార్జ్ చేయడం సాధ్యం కాదు"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"నిండింది"</string> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 130e1c4318a1..15e74ae1acf4 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"เชื่อมต่อแล้วผ่าน <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"พร้อมใช้งานผ่านทาง %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"แตะเพื่อลงชื่อสมัครใช้"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"ไม่มีอินเทอร์เน็ต"</string> <string name="private_dns_broken" msgid="1984159464346556931">"เข้าถึงเซิร์ฟเวอร์ DNS ไม่ได้"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"การเชื่อมต่อที่จำกัด"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"ไม่มีอินเทอร์เน็ต"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"ตาบอดจางสีแดง (สีแดง/เขียว)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"ตาบอดจางสีน้ำเงิน (สีน้ำเงิน/เหลือง)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"การแก้สี"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"การแก้ไขสีช่วยให้ผู้ที่มีอาการตาบอดสีเห็นสีต่างๆ ได้ตรงตามจริงยิ่งขึ้น"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"แทนที่โดย <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"เหลืออีกประมาณ <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> จนกว่าจะชาร์จ"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"ไม่ทราบ"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"กำลังชาร์จ"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"กำลังชาร์จ"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"ไม่ได้ชาร์จ"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"เสียบอยู่ ไม่สามารถชาร์จได้ในขณะนี้"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"เต็ม"</string> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index bc1a40560df0..801224492c1e 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Nakakonekta sa pamamagitan ng <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Available sa pamamagitan ng %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"I-tap para mag-sign up"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Walang internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Hindi ma-access ang pribadong DNS server"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Limitadong koneksyon"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Walang internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (pula-berde)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (asul-dilaw)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Pagtatama ng kulay"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ang pagwawasto ng kulay ay nakakatulong sa mga taong may color blindness na makita ang mga mas tamang kulay"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Na-override ng <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Humigit-kumulang <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ang natitira"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> hanggang matapos mag-charge"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Hindi Kilala"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Nagcha-charge"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"nagcha-charge"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hindi nagcha-charge"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Nakasaksak, hindi makapag-charge sa ngayon"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Puno"</string> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 4e263ef86cd5..b83ffcb16565 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> ile bağlandı"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s üzerinden kullanılabilir"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Kaydolmak için dokunun"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"İnternet yok"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Gizli DNS sunucusuna erişilemiyor"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Sınırlı bağlantı"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"İnternet yok"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Kırmızı renk körlüğü (kırmızı-yeşil)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Mavi renk körlüğü (mavi-sarı)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Renk düzeltme"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Renk düzeltme, renk körlüğü olan kişilerin daha doğru renkler görmelerine yardımcı olur"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> tarafından geçersiz kılındı"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Yaklaşık <xliff:g id="TIME_REMAINING">%1$s</xliff:g> kaldı"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - şarj olmaya <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"şarj oluyor"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Şarj olmuyor"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Prize takıldı, şu anda şarj olamıyor"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Dolu"</string> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index ad9f600ae74d..5fb46f8d8640 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Підключено через додаток <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Доступ через %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Торкніться, щоб увійти"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Немає Інтернету"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Немає доступу до приватного DNS-сервера"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Обмежене з’єднання"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Немає Інтернету"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Протаномалія (червоний – зелений)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Тританомалія (синій – жовтий)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Корекція кольору"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Корекція кольору допомагає людям із дальтонізмом бачити точніші кольори"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Замінено на <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Залишилося приблизно <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> до повного заряду"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Невідомо"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Заряджається"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"заряджається"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Не заряджається"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Підключено. Не вдається зарядити"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Акумулятор заряджено"</string> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index b995da1ad37b..c6c10f0fb8c9 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> کے ذریعے منسلک"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"دستیاب بذریعہ %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"سائن اپ کے لیے تھپتھپائیں"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"انٹرنیٹ نہیں ہے"</string> <string name="private_dns_broken" msgid="1984159464346556931">"نجی DNS سرور تک رسائی حاصل نہیں کی جا سکی"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"محدود کنکشن"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"انٹرنیٹ نہیں ہے"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (سرخ سبز)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (نیلا پیلا)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"رنگ کی اصلاح"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"رنگ کی درستگی رنگ نہ دکھائی دینے والے لوگوں کی رنگوں کو مزید درست طریقے سے دیکھنے میں مدد کرتی ہے"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> کے ذریعہ منسوخ کردیا گیا"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"تقریباً <xliff:g id="TIME_REMAINING">%1$s</xliff:g> باقی ہے"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> چارج ہونے تک"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"نامعلوم"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"چارج ہو رہا ہے"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"چارج ہو رہا ہے"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"چارج نہیں ہو رہا ہے"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"پلگ ان ہے، ابھی چارج نہیں کر سکتے"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"مکمل"</string> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index f02b63940115..07a6249b7e73 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"<xliff:g id="NAME">%1$s</xliff:g> orqali ulandi"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"%1$s orqali ishlaydi"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Yozilish uchun bosing"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Internetga ulanmagansiz"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Xususiy DNS server ishlamayapti"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Cheklangan aloqa"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Internet yo‘q"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaliya (qizil/yashil)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaliya (ko‘k/sariq)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Rangni tuzatish"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ranglarni sozlash ranglarni farqlashda muammosi bor insonlarga (masalan, daltoniklarga) aniq koʻrishda yordam beradi"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"<xliff:g id="TITLE">%1$s</xliff:g> bilan almashtirildi"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> – <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Taxminan <xliff:g id="TIME_REMAINING">%1$s</xliff:g> qoldi"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> ichida toʻliq quvvat oladi"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Noma’lum"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Quvvat olmoqda"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"quvvat olmoqda"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Quvvat olmayapti"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Ulangan, lekin quvvat olmayapti"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"To‘la"</string> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index 8fcf1f2ef21c..7e3944cb3349 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Đã kết nối qua <xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Có sẵn qua %1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Nhấn để đăng ký"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Không có Internet"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Không thể truy cập máy chủ DNS riêng tư"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Kết nối giới hạn"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Không có Internet"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Mù màu đỏ không hoàn toàn (đỏ-xanh lục)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Mù màu (xanh lam-vàng)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Sửa màu"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Tùy chọn sửa màu giúp những người bị mù màu thấy màu sắc chính xác hơn"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Bị ghi đè bởi <xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Còn khoảng <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> nữa là sạc xong"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Không xác định"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Đang sạc"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"đang sạc"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Hiện không sạc"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Đã cắm nhưng không thể sạc ngay"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Đầy"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 755cbeee3499..3edbb4d58b59 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"已通过<xliff:g id="NAME">%1$s</xliff:g>连接到网络"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可通过%1$s连接"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"点按即可注册"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"无法访问互联网"</string> <string name="private_dns_broken" msgid="1984159464346556931">"无法访问私人 DNS 服务器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"网络连接受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"无法访问互联网"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"红色弱视(红绿不分)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"蓝色弱视(蓝黄不分)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"颜色校正功能有助于色盲用户看到更准确的颜色"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已被“<xliff:g id="TITLE">%1$s</xliff:g>”覆盖"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"大约还可使用 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>后充满电"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"正在充电"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"正在充电"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"未在充电"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已插入电源,但是现在无法充电"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"电量充足"</string> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index e50acf5004fa..6174fe91a4a3 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"已透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 連線"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"輕按即可登入"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"沒有互聯網連線"</string> <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"連線受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有互聯網連線"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"紅色弱視 (紅綠)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"藍色弱視 (藍黃)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色彩校正有助色盲人士看到更準確的顏色"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已由「<xliff:g id="TITLE">%1$s</xliff:g>」覆寫"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"還有大約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - 還需 <xliff:g id="TIME">%2$s</xliff:g>才能充滿電"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"未知"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"正在充電"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已插入電源插座,但目前無法充電"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"電量已滿"</string> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 7df6fed4d8fd..a150d16f1b27 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"透過「<xliff:g id="NAME">%1$s</xliff:g>」連線"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"可透過 %1$s 使用"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"輕觸即可註冊"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"沒有網際網路連線"</string> <string name="private_dns_broken" msgid="1984159464346556931">"無法存取私人 DNS 伺服器"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"連線能力受限"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"沒有網際網路連線"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"紅色弱視 (紅-綠)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"藍色弱視 (藍-黃)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"色彩校正"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"色彩校正可協助色盲使用者看見較準確的色彩"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"已改為<xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"還能使用約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>後充飽電"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"不明"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"充電中"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"充電中"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"非充電中"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"已接上電源,但現在無法充電"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"電力充足"</string> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 46dffbe3aa33..003dda399d18 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -42,8 +42,7 @@ <string name="connected_via_app" msgid="3532267661404276584">"Ixhumeke nge-<xliff:g id="NAME">%1$s</xliff:g>"</string> <string name="available_via_passpoint" msgid="1716000261192603682">"Iyatholakala nge-%1$s"</string> <string name="tap_to_sign_up" msgid="5356397741063740395">"Thepha ukuze ubhalisele"</string> - <!-- no translation found for wifi_connected_no_internet (5087420713443350646) --> - <skip /> + <string name="wifi_connected_no_internet" msgid="5087420713443350646">"Ayikho i-inthanethi"</string> <string name="private_dns_broken" msgid="1984159464346556931">"Iseva eyimfihlo ye-DNS ayikwazi ukufinyelelwa"</string> <string name="wifi_limited_connection" msgid="1184778285475204682">"Iqoqo elikhawulelwe"</string> <string name="wifi_status_no_internet" msgid="3799933875988829048">"Ayikho i-inthanethi"</string> @@ -384,7 +383,8 @@ <string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"I-Protanomaly (bomvu-luhlaza)"</string> <string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"I-Tritanomaly (luhlaza okwesibhakabhaka-phuzi)"</string> <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Ukulungiswa kombala"</string> - <string name="accessibility_display_daltonizer_preference_subtitle" msgid="9137381746633858694">"Ukulungisa umbala kusiza abantu abangaboni imibala ukubona ngokuqondile"</string> + <!-- no translation found for accessibility_display_daltonizer_preference_subtitle (6178138727195403796) --> + <skip /> <string name="daltonizer_type_overridden" msgid="4509604753672535721">"Igitshezwe ngaphezulu yi-<xliff:g id="TITLE">%1$s</xliff:g>"</string> <string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string> <string name="power_remaining_duration_only" msgid="8264199158671531431">"Cishe u-<xliff:g id="TIME_REMAINING">%1$s</xliff:g> osele"</string> @@ -414,7 +414,10 @@ <string name="power_charging_duration" msgid="5005740040558984057">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ize igcwale"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Akwaziwa"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Iyashaja"</string> - <string name="battery_info_status_charging_lower" msgid="8696042568167401574">"iyashaja"</string> + <!-- no translation found for battery_info_status_charging_fast (8027559755902954885) --> + <skip /> + <!-- no translation found for battery_info_status_charging_slow (3190803837168962319) --> + <skip /> <string name="battery_info_status_discharging" msgid="6962689305413556485">"Ayishaji"</string> <string name="battery_info_status_not_charging" msgid="8330015078868707899">"Kuxhunyiwe, ayikwazi ukushaja khona manje"</string> <string name="battery_info_status_full" msgid="4443168946046847468">"Kugcwele"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 4d96251043ce..804e0cb021b7 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -553,6 +553,60 @@ <string name="enable_adb_summary">Debug mode when USB is connected</string> <!-- Setting title to revoke secure USB debugging authorizations --> <string name="clear_adb_keys">Revoke USB debugging authorizations</string> + <!-- [CHAR LIMIT=32] Setting title for ADB wireless switch --> + <string name="enable_adb_wireless">Wireless debugging</string> + <!-- [CHAR LIMIT=NONE] Setting checkbox summary for whether to enable Wireless debugging support on the phone --> + <string name="enable_adb_wireless_summary">Debug mode when Wi\u2011Fi is connected</string> + <!-- [CHAR LIMIT=32] Summary text when ADB wireless has error --> + <string name="adb_wireless_error">Error</string> + <!-- [CHAR LIMIT=32] Setting title for ADB wireless fragment --> + <string name="adb_wireless_settings">Wireless debugging</string> + <!-- [CHAR LIMIT=NONE] Wireless debugging settings. text displayed when wireless debugging is off and network list is empty. --> + <string name="adb_wireless_list_empty_off">To see and use available devices, turn on wireless debugging</string> + <!-- [CHAR LIMIT=50] Title for adb wireless pair by QR code preference --> + <string name="adb_pair_method_qrcode_title">Pair device with QR code</string> + <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by QR code preference --> + <string name="adb_pair_method_qrcode_summary">Pair new devices using QR code Scanner</string> + <!-- [CHAR LIMIT=50] Title for adb wireless pair by pairing code preference --> + <string name="adb_pair_method_code_title">Pair device with pairing code</string> + <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by pairing code preference --> + <string name="adb_pair_method_code_summary">Pair new devices using six digit code</string> + <!-- [CHAR LIMIT=50] Title for adb wireless paired devices category --> + <string name="adb_paired_devices_title">Paired devices</string> + <!-- [CHAR LIMIT=50] Summary for adb wireless paired device preference --> + <string name="adb_wireless_device_connected_summary">Currently connected</string> + <!-- [CHAR LIMIT=50] Title for the adb device details fragment --> + <string name="adb_wireless_device_details_title">Device details</string> + <!-- [CHAR LIMIT=16] Button label to forget an adb device --> + <string name="adb_device_forget">Forget</string> + <!-- [CHAR LIMIT=50] Title format for mac address preference in adb device details fragment --> + <string name="adb_device_fingerprint_title_format">Device fingerprint: <xliff:g id="fingerprint_param" example="a1:b2:c3:d4:e5:f6">%1$s</xliff:g></string> + <!-- [CHAR LIMIT=50] Title for adb wireless connection failed dialog --> + <string name="adb_wireless_connection_failed_title">Connection unsuccessful</string> + <!-- [CHAR LIMIT=NONE] Message for adb wireless connection failed dialog --> + <string name="adb_wireless_connection_failed_message">Make sure <xliff:g id="device_name" example="Bob's Macbook">%1$s</xliff:g> is connected to the correct network</string> + <!-- [CHAR LIMIT=32] Adb wireless pairing device dialog title --> + <string name="adb_pairing_device_dialog_title">Pair with device</string> + <!-- [CHAR LIMIT=32] Adb wireless pairing device dialog pairing code label --> + <string name="adb_pairing_device_dialog_pairing_code_label">Wi\u2011Fi pairing code</string> + <!-- [CHAR LIMIT=50] Adb Wireless pairing device failed dialog title --> + <string name="adb_pairing_device_dialog_failed_title">Pairing unsuccessful</string> + <!-- [CHAR LIMIT=NONE] Adb wireless pairing device failed dialog message --> + <string name="adb_pairing_device_dialog_failed_msg">Make sure the device is connected to the same network.</string> + <!-- [CHAR LIMIT=NONE] Adb wireless qr code scanner description --> + <string name="adb_wireless_qrcode_summary">Pair device over Wi\u2011Fi by scanning a QR code</string> + <!-- [CHAR LIMIT=NONE] Adb wireless QR code pairing in progress text --> + <string name="adb_wireless_verifying_qrcode_text">Pairing device\u2026</string> + <!-- [CHAR LIMIT=NONE] Adb wireless QR code failed message --> + <string name="adb_qrcode_pairing_device_failed_msg">Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network.</string> + <!-- [CHAR LIMIT=50] Adb Wireless ip address and port title --> + <string name="adb_wireless_ip_addr_preference_title">IP address \u0026 Port</string> + <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing scanner title --> + <string name="adb_wireless_qrcode_pairing_title">Scan QR code</string> + <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing description --> + <string name="adb_wireless_qrcode_pairing_description">Pair device over Wi\u2011Fi by scanning a QR Code</string> + <!--Adb wireless search Keywords [CHAR LIMIT=NONE]--> + <string name="keywords_adb_wireless">adb, debug, dev</string> <!-- [CHAR LIMIT=NONE] Setting checkbox title for Whether to include bug report item in power menu. --> <string name="bugreport_in_power">Bug report shortcut</string> <!-- [CHAR LIMIT=NONE] Setting checkbox summary for Whether to include bug report item in power --> @@ -689,6 +743,10 @@ <string name="adb_warning_title">Allow USB debugging?</string> <!-- Warning text to user about the implications of enabling USB debugging --> <string name="adb_warning_message">USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data.</string> + <!-- Title of warning dialog about the implications of enabling USB debugging [CHAR LIMIT=NONE] --> + <string name="adbwifi_warning_title">Allow wireless debugging?</string> + <!-- Warning text to user about the implications of enabling USB debugging [CHAR LIMIT=NONE] --> + <string name="adbwifi_warning_message">Wireless debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data.</string> <!-- Message of dialog confirming that user wants to revoke access to adb from all computers they have authorized --> <string name="adb_keys_warning_message">Revoke access to USB debugging from all computers you\u2019ve previously authorized?</string> <!-- Title of warning dialog about the implications of enabling developer settings --> diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index bfb79c05a432..954eb9bf3cc4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -181,6 +181,7 @@ public class AccessPoint implements Comparable<AccessPoint> { static final String KEY_SCANRESULTS = "key_scanresults"; static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache"; static final String KEY_CONFIG = "key_config"; + static final String KEY_PASSPOINT_UNIQUE_ID = "key_passpoint_unique_id"; static final String KEY_FQDN = "key_fqdn"; static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name"; static final String KEY_EAPTYPE = "eap_psktype"; @@ -217,7 +218,7 @@ public class AccessPoint implements Comparable<AccessPoint> { public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE; public static final String KEY_PREFIX_AP = "AP:"; - public static final String KEY_PREFIX_FQDN = "FQDN:"; + public static final String KEY_PREFIX_PASSPOINT_UNIQUE_ID = "PASSPOINT:"; public static final String KEY_PREFIX_OSU = "OSU:"; private final Context mContext; @@ -250,6 +251,7 @@ public class AccessPoint implements Comparable<AccessPoint> { * Information associated with the {@link PasspointConfiguration}. Only maintaining * the relevant info to preserve spaces. */ + private String mPasspointUniqueId; private String mFqdn; private String mProviderFriendlyName; private boolean mIsRoaming = false; @@ -308,6 +310,9 @@ public class AccessPoint implements Comparable<AccessPoint> { mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore); } } + if (savedState.containsKey(KEY_PASSPOINT_UNIQUE_ID)) { + mPasspointUniqueId = savedState.getString(KEY_PASSPOINT_UNIQUE_ID); + } if (savedState.containsKey(KEY_FQDN)) { mFqdn = savedState.getString(KEY_FQDN); } @@ -351,6 +356,7 @@ public class AccessPoint implements Comparable<AccessPoint> { */ public AccessPoint(Context context, PasspointConfiguration config) { mContext = context; + mPasspointUniqueId = config.getUniqueId(); mFqdn = config.getHomeSp().getFqdn(); mProviderFriendlyName = config.getHomeSp().getFriendlyName(); mSubscriptionExpirationTimeInMillis = config.getSubscriptionExpirationTimeInMillis(); @@ -371,6 +377,7 @@ public class AccessPoint implements Comparable<AccessPoint> { mContext = context; networkId = config.networkId; mConfig = config; + mPasspointUniqueId = config.getKey(); mFqdn = config.FQDN; setScanResultsPasspoint(homeScans, roamingScans); updateKey(); @@ -407,7 +414,7 @@ public class AccessPoint implements Comparable<AccessPoint> { if (isPasspoint()) { mKey = getKey(mConfig); } else if (isPasspointConfig()) { - mKey = getKey(mFqdn); + mKey = getKey(mPasspointUniqueId); } else if (isOsuProvider()) { mKey = getKey(mOsuProvider); } else { // Non-Passpoint AP @@ -677,19 +684,19 @@ public class AccessPoint implements Comparable<AccessPoint> { */ public static String getKey(WifiConfiguration config) { if (config.isPasspoint()) { - return getKey(config.FQDN); + return getKey(config.getKey()); } else { return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config)); } } /** - * Returns the AccessPoint key corresponding to a Passpoint network by its FQDN. + * Returns the AccessPoint key corresponding to a Passpoint network by its unique identifier. */ - public static String getKey(String fqdn) { + public static String getKey(String passpointUniqueId) { return new StringBuilder() - .append(KEY_PREFIX_FQDN) - .append(fqdn).toString(); + .append(KEY_PREFIX_PASSPOINT_UNIQUE_ID) + .append(passpointUniqueId).toString(); } /** @@ -766,7 +773,7 @@ public class AccessPoint implements Comparable<AccessPoint> { public boolean matches(WifiConfiguration config) { if (config.isPasspoint()) { - return (isPasspoint() && config.FQDN.equals(mConfig.FQDN)); + return (isPasspoint() && config.getKey().equals(mConfig.getKey())); } if (!ssid.equals(removeDoubleQuotes(config.SSID)) @@ -1052,7 +1059,7 @@ public class AccessPoint implements Comparable<AccessPoint> { public String getConfigName() { if (mConfig != null && mConfig.isPasspoint()) { return mConfig.providerFriendlyName; - } else if (mFqdn != null) { + } else if (mPasspointUniqueId != null) { return mProviderFriendlyName; } else { return ssid; @@ -1254,7 +1261,7 @@ public class AccessPoint implements Comparable<AccessPoint> { * Return true if this AccessPoint represents a Passpoint provider configuration. */ public boolean isPasspointConfig() { - return mFqdn != null && mConfig == null; + return mPasspointUniqueId != null && mConfig == null; } /** @@ -1310,8 +1317,12 @@ public class AccessPoint implements Comparable<AccessPoint> { if (info.isOsuAp() || mOsuStatus != null) { return (info.isOsuAp() && mOsuStatus != null); } else if (info.isPasspointAp() || isPasspoint()) { + // TODO: Use TextUtils.equals(info.getPasspointUniqueId(), mConfig.getKey()) when API + // is available return (info.isPasspointAp() && isPasspoint() - && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN)); + && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN) + && TextUtils.equals(info.getPasspointProviderFriendlyName(), + mConfig.providerFriendlyName)); } if (networkId != WifiConfiguration.INVALID_NETWORK_ID) { @@ -1377,6 +1388,9 @@ public class AccessPoint implements Comparable<AccessPoint> { if (mNetworkInfo != null) { savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo); } + if (mPasspointUniqueId != null) { + savedState.putString(KEY_PASSPOINT_UNIQUE_ID, mPasspointUniqueId); + } if (mFqdn != null) { savedState.putString(KEY_FQDN, mFqdn); } @@ -1949,11 +1963,11 @@ public class AccessPoint implements Comparable<AccessPoint> { return; } - String fqdn = passpointConfig.getHomeSp().getFqdn(); + String uniqueId = passpointConfig.getUniqueId(); for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing : wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) { WifiConfiguration config = pairing.first; - if (TextUtils.equals(config.FQDN, fqdn)) { + if (TextUtils.equals(config.getKey(), uniqueId)) { List<ScanResult> homeScans = pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); List<ScanResult> roamingScans = diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java index f21e466dd8ab..2fb2481ac117 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java @@ -84,7 +84,7 @@ public class TestAccessPointBuilder { bundle.putParcelable(AccessPoint.KEY_NETWORKINFO, mNetworkInfo); bundle.putParcelable(AccessPoint.KEY_WIFIINFO, mWifiInfo); if (mFqdn != null) { - bundle.putString(AccessPoint.KEY_FQDN, mFqdn); + bundle.putString(AccessPoint.KEY_PASSPOINT_UNIQUE_ID, mFqdn); } if (mProviderFriendlyName != null) { bundle.putString(AccessPoint.KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 26abf715369c..586c154179dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -606,7 +606,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro List<ScanResult> cachedScanResults = new ArrayList<>(mScanResultCache.values()); - // Add a unique Passpoint AccessPoint for each Passpoint profile's FQDN. + // Add a unique Passpoint AccessPoint for each Passpoint profile's unique identifier. accessPoints.addAll(updatePasspointAccessPoints( mWifiManager.getAllMatchingWifiConfigs(cachedScanResults), cachedAccessPoints)); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 1fe967b4750d..5458676e1061 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -646,16 +646,17 @@ android:label="Controls Providers" android:theme="@style/Theme.ControlsManagement" android:showForAllUsers="true" + android:clearTaskOnLaunch="true" android:excludeFromRecents="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> <activity android:name=".controls.management.ControlsFavoritingActivity" - android:parentActivityName=".controls.management.ControlsProviderSelectorActivity" android:theme="@style/Theme.ControlsManagement" android:excludeFromRecents="true" android:showForAllUsers="true" + android:finishOnTaskLaunch="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> diff --git a/packages/SystemUI/docs/QS-QQS.png b/packages/SystemUI/docs/QS-QQS.png Binary files differnew file mode 100644 index 000000000000..02de479cb8c0 --- /dev/null +++ b/packages/SystemUI/docs/QS-QQS.png diff --git a/packages/SystemUI/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md new file mode 100644 index 000000000000..b48ba6708313 --- /dev/null +++ b/packages/SystemUI/docs/qs-tiles.md @@ -0,0 +1,377 @@ +# Quick Settings Tiles (almost all there is to know about them) + +[TOC] + +## About this document + +This document is a more or less comprehensive summary of the state and infrastructure used by Quick Settings tiles. It provides descriptions about the lifecycle of a tile, how to create new tiles and how SystemUI manages and displays tiles, among other topics. + +## What are Quick Settings Tiles? + +Quick Settings (from now on, QS) is the expanded panel that contains shortcuts for the user to toggle many settings. This is opened by expanding the notification drawer twice (or once when phone is locked). Quick Quick Settings (QQS) is the smaller panel that appears on top of the notifications before expanding twice and contains some of the toggles with no text. + +Each of these toggles that appear either in QS or QQS are called Quick Settings Tiles (or tiles for short). They allow the user to enable or disable settings quickly and sometimes provides access to more comprehensive settings pages. + +The following image shows QQS on the left and QS on the right, with the tiles highlighted. + + + +QS Tiles usually depend on one or more Controllers that bind the tile with the necessary service. Controllers are obtained by the backend and used for communication between the user and the device. + +### A note on multi-user support + +All the classes described in this document that live inside SystemUI are only instantiated in the process of user 0. The different controllers that back the QS Tiles (also instantiated just in user 0) are user aware and provide an illusion of different instances for different users. + +For an example on this, see [`RotationLockController`](/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java). This controller for the `RotationLockTile` listens to changes in all users. + +## What are tiles made of? + +### Tile backend + +QS Tiles are composed of the following backend classes. + +* [`QSTile`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java): Interface providing common behavior for all Tiles. This class also contains some useful utility classes needed for the tiles. + * `Icon`: Defines the basic interface for an icon as used by the tiles. + * `State`: Encapsulates the state of the Tile in order to communicate between the backend and the UI. +* [`QSTileImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java): Abstract implementation of `QSTile`, providing basic common behavior for all tiles. Also implements extensions for different types of `Icon`. All tiles currently defined in SystemUI subclass from this implementation. +* [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles): Each tile from SystemUI is defined here by a class that extends `QSTileImpl`. These implementations connect to corresponding controllers. The controllers serve two purposes: + * track the state of the device and notify the tile when a change has occurred (for example, bluetooth connected to a device) + * accept actions from the tiles to modify the state of the phone (for example, enablind and disabling wifi). +* [`CustomTile`](/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java): Equivalent to the tiles in the previous item, but used for 3rd party tiles. In depth information to be found in [`CustomTile`](#customtile) + +All the elements in SystemUI that work with tiles operate on `QSTile` or the interfaces defined in it. However, all the current implementations of tiles in SystemUI subclass from `QSTileImpl`, as it takes care of many common situations. Throughout this document, we will focus on `QSTileImpl` as examples of tiles. + +The interfaces in `QSTile` as well as other interfaces described in this document can be used to implement plugins to add additional tiles or different behavior. For more information, see [plugins.md](plugins.md) + +#### Tile State + +Each tile has an associated `State` object that is used to communicate information to the corresponding view. The base class `State` has (among others) the following fields: + +* **`state`**: one of `Tile#STATE_UNAVAILABLE`, `Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`. +* **`icon`**; icon to display. It may depend on the current state. +* **`label`**: usually the name of the tile. +* **`secondaryLabel`**: text to display in a second line. Usually extra state information. +* **`contentDescription`** +* **`expandedAccessibilityClassName`**: usually `Switch.class.getName()` for boolean Tiles. This will make screen readers read the current state of the tile as well as the new state when it's toggled. For this, the Tile has to use `BooleanState`. +* **`handlesLongClick`**: whether the Tile will handle long click. If it won't, it should be set to `false` so it will not be announced for accessibility. + +Setting any of these fields during `QSTileImpl#handleUpdateState` will update the UI after it. + +Additionally. `BooleanState` has a `value` boolean field that usually would be set to `state == Tile#STATE_ACTIVE`. This is used by accessibility services along with `expandedAccessibilityClassName`. + +#### SystemUI tiles + +Each tile defined in SystemUI extends `QSTileImpl`. This abstract class implements some common functions and leaves others to be implemented by each tile, in particular those that determine how to handle different events (refresh, click, etc.). + +For more information on how to implement a tile in SystemUI, see [Implementing a SystemUI tile](#implementing-a-systemui-tile). + +### Tile views + +Each Tile has a couple of associated views for displaying it in QS and QQS. These views are updated after the backend updates the `State` using `QSTileImpl#handleUpdateState`. + +* **[`com.android.systemui.plugins.qs.QSTileView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java)**: Abstract class that provides basic Tile functionality. These allows external [Factories](#qsfactory) to create Tiles. +* **[`QSTileBaseView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java)**: Implementation of `QSTileView` used in QQS that takes care of most of the features of the view: + * Holding the icon + * Background color and shape + * Ripple + * Click listening +* **[`QSTileView`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java)**: Extends `QSTileBaseView`to add label support. Used in QS. +* **[`QSIconView`](/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java)** +* **[`QSIconViewImpl`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java)** + +#### QSIconView and QSIconViewImpl + +`QSIconView` is an interface that define the basic actions that icons have to respond to. Its base implementation in SystemUI is `QSIconViewImpl` and it and its subclasses are used by all QS tiles. + +This `ViewGroup` is a container for the icon used in each tile. It has methods to apply the current `State` of the tile, modifying the icon (color and animations). Classes that inherit from this can add other details that are modified when the `State` changes. + +Each `QSTileImpl` can specify that they use a particular implementation of this class when creating an icon. + +### How are the backend and the views related? + +The backend of the tiles (all the implementations of `QSTileImpl`) communicate with the views by using a `State`. The backend populates the state, and then the view maps the state to a visual representation. + +It's important to notice that the state of the tile (internal or visual) is not directly modified by a user action like clicking on the tile. Instead, acting on a tile produces internal state changes on the device, and those trigger the changes on the tile state and UI. + +When a container for tiles (`QuickQSPanel` or `QSPanel`) has to display tiles, they create a [`TileRecord`](/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java). This associates the corresponding `QSTile` with its `QSTileView`, doing the following: + +* Create the corresponding `QSTileView` to display in that container. +* Create a callback for `QSTile` to call when its state changes. Note that a single tile will normally have up to two callbacks: one for QS and one for QQS. + +#### Life of a tile click + +This is a brief run-down of what happens when a user clicks on a tile. Internal changes on the device (for example, changes from Settings) will trigger this process starting in step 3. Throughout this section, we assume that we are dealing with a `QSTileImpl`. + +1. User clicks on tile. The following calls happen in sequence: + 1. `QSTileBaseView#onClickListener`. + 2. `QSTile#click`. + 3. `QSTileImpl#handleClick`. This last call sets the new state for the device by using the associated controller. +2. State in the device changes. This is normally outside of SystemUI's control. +3. Controller receives a callback (or `Intent`) indicating the change in the device. The following calls happen: + 1. `QSTileImpl#refreshState`, maybe passing an object with necessary information regarding the new state. + 2. `QSTileImpl#handleRefreshState` +4. `QSTileImpl#handleUpdateState` is called to update the state with the new information. This information can be obtained both from the `Object` passed to `refreshState` as well as from the controller. +5. If the state has changed (in at least one element), `QSTileImpl#handleStateChanged` is called. This will trigger a call to all the associated `QSTile.Callback#onStateChanged`, passing the new `State`. +6. `QSTileView#onStateChanged` is called and this calls `QSTileView#handleStateChanged`. This method maps the state into the view: + * The tile is rippled and the color changes to match the new state. + * `QSIconView.setIcon` is called to apply the correct state to the icon and the correct icon to the view. + * If the tile is a `QSTileView` (in expanded QS), the labels are changed. + +## Third party tiles (TileService) + +A third party tile is any Quick Settings tile that is provided by an app (that's not SystemUI). This is implemented by developers subclassing [`TileService`](/core/java/android/service/quicksettings/TileService.java) and interacting with its API. + +### API classes + +The classes that define the public API are in [core/java/android/service/quicksettings](core/java/android/service/quicksettings). + +#### Tile + +Parcelable class used to communicate information about the state between the external app and SystemUI. The class supports the following fields: + +* Label +* Subtitle +* Icon +* State (`Tile#STATE_ACTIVE`, `Tile#STATE_INACTIVE`, `Tile#STATE_UNAVAILABLE`) +* Content description + +Additionally, it provides a method to notify SystemUI that the information may have changed and the tile should be refreshed. + +#### TileService + +This is an abstract Service that needs to be implemented by the developer. The Service manifest must have the permission `android.permission.BIND_QUICK_SETTINGS_TILE` and must respond to the action `android.service.quicksettings.action.QS_TILE`. This will allow SystemUI to find the available tiles and display them to the user. + +The implementer is responsible for creating the methods that will respond to the following calls from SystemUI: + +* **`onTileAdded`**: called when the tile is added to QS. +* **`onTileRemoved`**: called when the tile is removed from QS. +* **`onStartListening`**: called when QS is opened and the tile is showing. This marks the start of the window when calling `getQSTile` is safe and will provide the correct object. +* **`onStopListening`**: called when QS is closed or the tile is no longer visible by the user. This marks the end of the window described in `onStartListening`. +* **`onClick`**: called when the user clicks on the tile. + +Additionally, the following final methods are provided: + +* ```java + public final Tile getQsTile() + ``` + + Provides the tile object that can be modified. This should only be called in the window between `onStartListening` and `onStopListening`. + +* ```java + public final boolean isLocked() + + public final boolean isSecure() + ``` + + Provide information about the secure state of the device. This can be used by the tile to accept or reject actions on the tile. + +* ```java + public final void unlockAndRun(Runnable) + ``` + + May prompt the user to unlock the device if locked. Once the device is unlocked, it runs the given `Runnable`. + +* ```java + public final void showDialog(Dialog) + ``` + + Shows the provided dialog. + +##### Binding + +When the Service is bound, a callback Binder is provided by SystemUI for all the callbacks, as well as an identifier token (`Binder`). This token is used in the callbacks to identify this `TileService` and match it to the corresponding tile. + +The tiles are bound once immediately on creation. After that, the tile is bound whenever it should start listening. When the panels are closed, and the tile is set to stop listening, it will be unbound after a delay of `TileServiceManager#UNBIND_DELAY` (30s), if it's not set to listening again. + +##### Active tile + +A `TileService` can be declared as an active tile by adding specific meta-data to its manifest (see [TileService#META_DATA_ACTIVE_TILE](https://developer.android.com/reference/android/service/quicksettings/TileService#META_DATA_ACTIVE_TILE)). In this case, it won't receive a call of `onStartListening` when QS is opened. Instead, the tile must request listening status by making a call to `TileService#requestListeningState` with its component name. This will initiate a window that will last until the tile is updated. + +The tile will also be granted listening status if it's clicked by the user. + +### SystemUI classes + +The following sections describe the classes that live in SystemUI to support third party tiles. These classes live in [SystemUI/src/com/android/systemui/qs/external](/packages/SystemUI/src/com/android/systemui/qs/external/) + +#### CustomTile + +This class is an subclass of `QSTileImpl` to be used with third party tiles. It provides similar behavior to SystemUI tiles as well as handling exclusive behavior like lifting default icons and labels from the application manifest. + +#### TileServices + +This class is the central controller for all tile services that are currently in Quick Settings as well as provides the support for starting new ones. It is also an implementation of the `Binder` that receives all calls from current `TileService` components and dispatches them to SystemUI or the corresponding `CustomTile`. + +Whenever a binder call is made to this class, it matches the corresponding token assigned to the `TileService` with the `ComponentName` and verifies that the call comes from the right UID to prevent spoofing. + +As this class is the only one that's aware of every `TileService` that's currently bound, it is also in charge of requesting some to be unbound whenever there is a low memory situation. + +#### TileLifecycleManager + +This class is in charge of binding and unbinding to a particular `TileService` when necessary, as well as sending the corresponding binder calls. It does not decide whether the tile should be bound or unbound, unless it's requested to process a message. It additionally handles errors in the `Binder` as well as changes in the corresponding component (like updates and enable/disable). + +The class has a queue that stores requests while the service is not bound, to be processed as soon as the service is bound. + +Each `TileService` gets assigned an exclusive `TileLifecycleManager` when its corresponding tile is added to the set of current ones and kept as long as the tile is available to the user. + +#### TileServiceManager + +Each instance of this class is an intermediary between the `TileServices` controller and a `TileLifecycleManager` corresponding to a particular `TileService`. + +This class handles management of the service, including: + +* Deciding when to bind and unbind, requesting it to the `TileLifecycleManager`. +* Relaying messages to the `TileService` through the `TileLifecycleManager`. +* Determining the service's bind priority (to deal with OOM situations). +* Detecting when the package/component has been removed in order to remove the tile and references to it. + +## How are tiles created/instantiated? + +This section describes the classes that aid in the creation of each tile as well as the complete lifecycle of a tile. First we describe two important interfaces/classes. + +### QSTileHost + +This class keeps track of the tiles selected by the current user (backed in the Secure Setting `sysui_qs_tiles`) to be displayed in Quick Settings. Whenever the value of this setting changes (or on device start), the whole list of tiles is read. This is compared with the current tiles, destroying unnecessary ones and creating needed ones. + +It additionally provides a point of communication between the tiles and the StatusBar, for example to open it and collapse it. And a way for the StatusBar service to add tiles (only works for `CustomTile`). + +#### Tile specs + +Each single tile is identified by a spec, which is a unique String for that type of tile. The current tiles are stored as a Setting string of comma separated values of these specs. Additionally, the default tiles (that appear on a fresh system) configuration value is stored likewise. + +SystemUI tile specs are usually a single simple word identifying the tile (like `wifi` or `battery`). Custom tile specs are always a string of the form `custom(...)` where the ellipsis is a flattened String representing the `ComponentName` for the corresponding `TileService`. + +### QSFactory + +This interface provides a way of creating tiles and views from a spec. It can be used in plugins to provide different definitions for tiles. + +In SystemUI there is only one implementation of this factory and that is the default factory (`QSFactoryImpl`) in `QSTileHost`. + +#### QSFactoryImpl + +This class implements two methods as specified in the `QSFactory` interface: + +* ```java + public QSTile createTile(String) + ``` + + Creates a tile (backend) from a given spec. The factory has providers for all of the SystemUI tiles, returning one when the correct spec is used. + + If the spec is not recognized but it has the `custom(` prefix, the factory tries to create a `CustomTile` for the component in the spec. This could fail (the component is not a valid `TileService` or is not enabled) and will be detected later when the tile is polled to determine if it's available. + +* ```java + public QSTileView createTileView(QSTile, boolean) + ``` + + Creates a view for the corresponding `QSTile`. The second parameter determines if the view that is created should be a collapsed one (for using in QQS) or not (for using in QS). + +### Lifecycle of a Tile + +We describe first the parts of the lifecycle that are common to SystemUI tiles and third party tiles. Following that, there will be a section with the steps that are exclusive to third party tiles. + +1. The tile is added through the QS customizer by the user. This will immediately save the new list of tile specs to the Secure Setting `sysui_qs_tiles`. This step could also happend if `StatusBar` adds tiles (either through adb, or through its service interface as with the `DevelopmentTiles`). +2. This triggers a "setting changed" that is caught by `QSTileHost`. This class processes the new value of the setting and finds out that there is a new spec in the list. Alternatively, when the device is booted, all tiles in the setting are considered as "new". +3. `QSTileHost` calls all the available `QSFactory` classes that it has registered in order to find the first one that will be able to create a tile with that spec. Assume that `QSFactoryImpl` managed to create the tile, which is some implementation of `QSTile` (either a SystemUI subclass of `QSTileImpl` or a `CustomTile`). If the tile is available, it's stored in a map and things proceed forward. +4. `QSTileHost` calls its callbacks indicating that the tiles have changed. In particular, `QSPanel` and `QuickQSPanel` receive this call with the full list of tiles. We will focus on these two classes. +5. For each tile in this list, a `QSTileView` is created (collapsed or expanded) and attached to a `TileRecord` containing the tile backend and the view. Additionally: + * a callback is attached to the tile to communicate between the backend and the view or the panel. + * the click listeners in the tile are attached to those of the view. +6. The tile view is added to the corresponding layout. + +When the tile is removed from the list of current tiles, all these classes are properly disposed including removing the callbacks and making sure that the backends remove themselves from the controllers they were listening to. + +#### Lifecycle of a CustomTile + +In step 3 of the previous process, when a `CustomTile` is created, additional steps are taken to ensure the proper binding to the service as described in [Third party tiles (TileService)](#third-party-tiles-tileservice). + +1. The `CustomTile` obtains the `TileServices` class from the `QSTileHost` and request the creation of a `TileServiceManager` with its token. As the spec for the `CustomTile` contains the `ComponentName` of the associated service, this can be used to bind to it. +2. The `TileServiceManager` creates its own `TileLifecycleManager` to take care of binding to the service. +3. `TileServices` creates maps between the token, the `CustomTile`, the `TileServiceManager`, the token and the `ComponentName`. + +## Implementing a tile + +This section describes necessary and recommended steps when implementing a Quick Settings tile. Some of them are optional and depend on the requirements of the tile. + +### Implementing a SystemUI tile + +1. Create a class (preferably in [`SystemUI/src/com/android/systemui/qs/tiles`](/packages/SystemUI/src/com/android/systemui/qs/tiles)) implementing `QSTileImpl` with a particular type of `State` as a parameter. +2. Create an injectable constructor taking a `QSHost` and whichever classes are needed for the tile's operation. Normally this would be other SystemUI controllers. +3. Implement the methods described in [Abstract methods in QSTileImpl](#abstract-methods-in-qstileimpl). Look at other tiles for help. Some considerations to have in mind: + * If the tile will not support long click (like the `FlashlightTile`), set `state.handlesLongClick` to `false` (maybe in `newTileState`). + * Changes to the tile state (either from controllers or from clicks) should call `refreshState`. + * Use only `handleUpdateState` to modify the values of the state to the new ones. This can be done by polling controllers or through the `arg` parameter. + * If the controller is not a `CallbackController`, respond to `handleSetListening` by attaching/dettaching from controllers. + * Implement `isAvailable` so the tile will not be created when it's not necessary. +4. In `QSFactoryImpl`: + * Inject a `Provider` for the tile created before. + * Add a case to the `switch` with a unique String spec for the chosen tile. +5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles. + +#### Abstract methods in QSTileImpl + +Following are methods that need to be implemented when creating a new SystemUI tile. `TState` is a type variable of type `State`. + +* ```java + public TState newTileState() + ``` + + Creates a new `State` for this tile to use. Each time the state changes, it is copied into a new one and the corresponding fields are modified. The framework provides `State`, `BooleanState` (has an on and off state and provides this as a content description), `SignalState` (`BooleanState` with `activityIn` and `activityOut`), and `SlashState` (can be rotated or slashed through). + + If a tile has special behavior (no long click, no ripple), it can be set in its state here. + +* ```java + public void handleSetListening(boolean) + ``` + + Initiates or terminates listening behavior, like listening to Callbacks from controllers. This gets triggered when QS is expanded or collapsed (i.e., when the tile is visible and actionable). Most tiles (like `WifiTile`) do not implement this. Instead, Tiles are LifecycleOwner and are marked as `RESUMED` or `DESTROYED` in `QSTileImpl#handleListening` and handled as part of the lifecycle of [CallbackController](/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackController.java) + +* ```java + public QSIconView createTileView(Context) + ``` + + Allows a Tile to use a `QSIconView` different from `QSIconViewImpl` (see [Tile views](#tile-views)), which is the default defined in `QSTileImpl` + +* ```java + public Intent getLongClickIntent() + ``` + + Determines the `Intent` launched when the Tile is long pressed. + +* ```java + protected void handleClick() + + protected void handleSecondaryClick() + + protected void handleLongClick() + ``` + + Handles what to do when the Tile is clicked. In general, a Tile will make calls to its controller here and maybe update its state immediately (by calling `QSTileImpl#refreshState`). A Tile can also decide to ignore the click here, if it's `Tile#STATE_UNAVAILABLE`. + + By default long click redirects to click and long click launches the intent defined in `getLongClickIntent`. + +* ```java + protected void handleUpdateState(TState, Object) + ``` + + Updates the `State` of the Tile based on the state of the device as provided by the respective controller. It will be called every time the Tile becomes visible, is interacted with or `QSTileImpl#refreshState` is called. After this is done, the updated state will be reflected in the UI. + +* ```java + public int getMetricsCategory() + ``` + + Identifier for this Tile, as defined in [proto/src/metrics_constants/metrics_constants.proto](/proto/src/metrics_constants/metrics_constants.proto). This is used to log events related to this Tile. + +* ```java + public boolean isAvailable() + ``` + + Determines if a Tile is available to be used (for example, disable `WifiTile` in devices with no Wifi support). If this is false, the Tile will be destroyed upon creation. + +* ```java + public CharSequence getTileLabel() + ``` + + Provides a default label for this Tile. Used by the QS Panel customizer to show a name next to each available tile. + +### Implementing a third party tile + +For information about this, use the Android Developer documentation for [TileService](https://developer.android.com/reference/android/service/quicksettings/TileService).
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/controls_management.xml b/packages/SystemUI/res-keyguard/layout/controls_management.xml deleted file mode 100644 index 8330258e2456..000000000000 --- a/packages/SystemUI/res-keyguard/layout/controls_management.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center_horizontal" - android:paddingTop="@dimen/controls_management_top_padding" - android:paddingStart="@dimen/controls_management_side_padding" - android:paddingEnd="@dimen/controls_management_side_padding" > - - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceLarge" - android:textSize="@dimen/controls_title_size" - android:textAlignment="center" /> - - <TextView - android:id="@+id/subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/controls_management_titles_margin" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textAlignment="center" /> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/list" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/controls_management_list_margin" /> - -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml new file mode 100644 index 000000000000..7b43a0315c10 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.Control.Title" + android:textColor="?android:attr/colorPrimary" + android:layout_marginStart="12dp" + android:layout_marginEnd="2dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="4dp"> + +</TextView>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable-nodpi/android_11_dial.xml b/packages/SystemUI/res/drawable-nodpi/android_11_dial.xml new file mode 100644 index 000000000000..73fd37f1bdd6 --- /dev/null +++ b/packages/SystemUI/res/drawable-nodpi/android_11_dial.xml @@ -0,0 +1,63 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:pathData="M77.773,51.064h-1.583c-0.217,0 -0.393,-0.176 -0.393,-0.393v-1.46c0,-0.217 0.176,-0.393 0.393,-0.393h3.466c0.217,0 0.393,0.176 0.393,0.393v9.921c0,0.217 -0.176,0.393 -0.393,0.393h-1.49c-0.217,0 -0.393,-0.176 -0.393,-0.393V51.064z" + android:fillColor="#F86734"/> + <path + android:pathData="M83.598,51.064h-1.583c-0.217,0 -0.393,-0.176 -0.393,-0.393v-1.46c0,-0.217 0.176,-0.393 0.393,-0.393h3.466c0.217,0 0.393,0.176 0.393,0.393v9.921c0,0.217 -0.176,0.393 -0.393,0.393h-1.49c-0.217,0 -0.393,-0.176 -0.393,-0.393V51.064z" + android:fillColor="#F86734"/> + <path + android:pathData="M70.044,75.974m-0.644,0a0.644,0.644 0,1 1,1.288 0a0.644,0.644 0,1 1,-1.288 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M56.896,80.985m-0.718,0a0.718,0.718 0,1 1,1.436 0a0.718,0.718 0,1 1,-1.436 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M43.408,78.881m-0.795,0a0.795,0.795 0,1 1,1.59 0a0.795,0.795 0,1 1,-1.59 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M32.419,70.115m-0.874,0a0.874,0.874 0,1 1,1.748 0a0.874,0.874 0,1 1,-1.748 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M27.306,56.992m-0.954,0a0.954,0.954 0,1 1,1.908 0a0.954,0.954 0,1 1,-1.908 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M29.313,43.489m-1.036,0a1.036,1.036 0,1 1,2.072 0a1.036,1.036 0,1 1,-2.072 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M37.988,32.445m-1.118,0a1.118,1.118 0,1 1,2.236 0a1.118,1.118 0,1 1,-2.236 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M51.137,27.064m-1.201,0a1.201,1.201 0,1 1,2.402 0a1.201,1.201 0,1 1,-2.402 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M64.553,28.868m-1.284,0a1.284,1.284 0,1 1,2.568 0a1.284,1.284 0,1 1,-2.568 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M75.522,37.652m-1.368,0a1.368,1.368 0,1 1,2.736 0a1.368,1.368 0,1 1,-2.736 0" + android:fillColor="#d7effe"/> + <path + android:pathData="M87.942,115.052l-47.557,-47.557l26.869,-26.87l47.557,47.558z"> + <aapt:attr name="android:fillColor"> + <gradient + android:startY="56.087" + android:startX="55.8464" + android:endY="100.0297" + android:endX="99.7891" + android:type="linear"> + <item android:offset="0" android:color="#3F000000"/> + <item android:offset="1" android:color="#00000000"/> + </gradient> + </aapt:attr> + </path> + <path + android:pathData="M53.928,54.17m-18.999,0a18.999,18.999 0,1 1,37.998 0a18.999,18.999 0,1 1,-37.998 0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M66.353,54.17m-3.185,0a3.185,3.185 0,1 1,6.37 0a3.185,3.185 0,1 1,-6.37 0" + android:fillColor="#FFFFFF"/> +</vector> diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml index 7a68c032d8be..7f8d4fa8833f 100644 --- a/packages/SystemUI/res/drawable-nodpi/icon.xml +++ b/packages/SystemUI/res/drawable-nodpi/icon.xml @@ -15,5 +15,5 @@ Copyright (C) 2018 The Android Open Source Project --> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/icon_bg"/> - <foreground android:drawable="@drawable/q"/> + <foreground android:drawable="@drawable/android_11_dial"/> </adaptive-icon> diff --git a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml index 2a54dfad8191..31b2a7f9a333 100644 --- a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml +++ b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml @@ -14,5 +14,5 @@ Copyright (C) 2018 The Android Open Source Project limitations under the License. --> <color xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#77C360" /> + android:color="#073042" /> diff --git a/packages/SystemUI/res/drawable-nodpi/q.xml b/packages/SystemUI/res/drawable-nodpi/q.xml deleted file mode 100644 index 0f42d2e20040..000000000000 --- a/packages/SystemUI/res/drawable-nodpi/q.xml +++ /dev/null @@ -1,40 +0,0 @@ -<!-- -Copyright (C) 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="108dp" - android:height="108dp" - android:viewportWidth="108.0" - android:viewportHeight="108.0"> - <group - android:name="scale" - android:pivotX="54" android:pivotY="54" - android:scaleX="0.9" - android:scaleY="0.9"> - <group - android:name="nudge" - android:translateX="24" - android:translateY="23.5"> - <path - android:name="tail" - android:fillColor="#FFFFFF" - android:pathData="M21.749674,34.122784l-9.431964,9.529709l-6.31771,-6.2529106l15.736504,-15.899582l64.765724,65.16436l-6.3046494,6.266083z"/> - <path - android:name="counter" - android:fillColor="#FFFFFF" - android:pathData="M30,9.32352941 C41.6954418,9.32352941 51.1764706,18.8045582 51.1764706,30.5 C51.1764706,42.1954418 41.6954418,51.6764706 30,51.6764706 C18.3045582,51.6764706 8.82352941,42.1954418 8.82352941,30.5 C8.82352941,18.8045582 18.3045582,9.32352941 30,9.32352941 L30,9.32352941 Z M30,0.5 C13.4314575,0.5 -5.53805368e-15,13.9314575 -7.10542736e-15,30.5 C-1.02401747e-14,47.0685425 13.4314575,60.5 30,60.5 C46.5685425,60.5 60,47.0685425 60,30.5 C59.9805514,13.9395201 46.5604799,0.519448617 30,0.5 Z"/> - </group> - </group> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_cancel_24.xml b/packages/SystemUI/res/drawable/ic_cancel_24.xml new file mode 100644 index 000000000000..8ab28ddb680d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_cancel_24.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_important.xml b/packages/SystemUI/res/drawable/ic_important.xml new file mode 100644 index 000000000000..d7439e167dd4 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_important.xml @@ -0,0 +1,26 @@ +<?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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M4,18.99h11c0.67,0 1.27,-0.32 1.63,-0.83L21,12l-4.37,-6.16C16.27,5.33 15.67,5 15,5H4l5,7 -5,6.99z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_important_outline.xml b/packages/SystemUI/res/drawable/ic_important_outline.xml new file mode 100644 index 000000000000..7a628bb65433 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_important_outline.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. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M15,19L3,19l4.5,-7L3,5h12c0.65,0 1.26,0.31 1.63,0.84L21,12l-4.37,6.16c-0.37,0.52 -0.98,0.84 -1.63,0.84zM6.5,17L15,17l3.5,-5L15,7L6.5,7l3.5,5 -3.5,5z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml deleted file mode 100644 index 4a731b35b423..000000000000 --- a/packages/SystemUI/res/drawable/ic_star.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="@android:color/white" - android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml deleted file mode 100644 index 9ede40be3b7b..000000000000 --- a/packages/SystemUI/res/drawable/ic_star_border.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="@android:color/white" - android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/screenshot_cancel.xml new file mode 100644 index 000000000000..be3c5983bb2e --- /dev/null +++ b/packages/SystemUI/res/drawable/screenshot_cancel.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:pathData="M24,24m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0" + android:fillColor="@android:color/white"/> + <path + android:fillColor="@color/GM2_grey_500" + android:pathData="M31,18.41L29.59,17 24,22.59 18.41,17 17,18.41 22.59,24 17,29.59 18.41,31 24,25.41 29.59,31 31,29.59 25.41,24z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index 68c824698b2d..823bbcd6e68c 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -72,8 +72,8 @@ <CheckBox android:id="@+id/favorite" android:visibility="gone" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="48dp" + android:layout_height="48dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml new file mode 100644 index 000000000000..a7379bedebef --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal" + android:paddingTop="@dimen/controls_management_top_padding" + android:paddingStart="@dimen/controls_management_side_padding" + android:paddingEnd="@dimen/controls_management_side_padding" > + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textSize="@dimen/controls_title_size" + android:textAlignment="center" /> + + <TextView + android:id="@+id/subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/controls_management_titles_margin" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textAlignment="center" /> + + <androidx.core.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:layout_marginTop="@dimen/controls_management_list_margin"> + + <ViewStub + android:id="@+id/stub" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + </androidx.core.widget.NestedScrollView> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="64dp"> + + <View + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="4dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:text="See other apps" + android:textAppearance="@style/TextAppearance.Control.Title" + android:textColor="?android:attr/colorPrimary" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent"/> + + <Button + android:id="@+id/done" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:text="Done" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent"/> + </androidx.constraintlayout.widget.ConstraintLayout> + </FrameLayout> + + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml new file mode 100644 index 000000000000..2bab433d21f3 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management_apps.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<androidx.recyclerview.widget.RecyclerView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + > + +</androidx.recyclerview.widget.RecyclerView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml new file mode 100644 index 000000000000..a36dd1247a04 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management_favorites.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <TextView + android:id="@+id/text_favorites" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="FAVORITES" + android:textAppearance="?android:attr/textAppearanceSmall" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@id/divider1" + app:layout_constraintTop_toTopOf="parent" + /> + + <View + android:id="@+id/divider1" + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/listFavorites" + app:layout_constraintTop_toBottomOf="@id/text_favorites" + /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/listFavorites" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:nestedScrollingEnabled="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/text_all" + app:layout_constraintTop_toBottomOf="@id/divider1"/> + + <TextView + android:id="@+id/text_all" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:text="ALL" + android:textAppearance="?android:attr/textAppearanceSmall" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@id/divider2" + app:layout_constraintTop_toBottomOf="@id/listFavorites" + /> + + <View + android:id="@+id/divider2" + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/listAll" + app:layout_constraintTop_toBottomOf="@id/text_all" + /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/listAll" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:nestedScrollingEnabled="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/divider2"/> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index 40b2476941c2..2cd9505b8fe4 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -1,3 +1,18 @@ +<!-- + ~ 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. + --> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 1f7def2bc956..d0151fff95c4 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -55,10 +55,22 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:elevation="8dp" + android:elevation="@dimen/screenshot_preview_elevation" android:visibility="gone" android:background="@drawable/screenshot_rounded_corners" android:adjustViewBounds="true"/> + <FrameLayout + android:id="@+id/global_screenshot_dismiss_button" + android:layout_width="@dimen/screenshot_dismiss_button_tappable_size" + android:layout_height="@dimen/screenshot_dismiss_button_tappable_size" + android:elevation="9dp" + android:visibility="gone"> + <ImageView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="@dimen/screenshot_dismiss_button_margin" + android:src="@drawable/screenshot_cancel"/> + </FrameLayout> <ImageView android:id="@+id/global_screenshot_flash" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml index a9d6e3575317..84606126086d 100644 --- a/packages/SystemUI/res/layout/notification_conversation_info.xml +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -28,7 +28,7 @@ android:paddingStart="@*android:dimen/notification_content_margin_start"> <!-- Package Info --> - <RelativeLayout + <LinearLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="@dimen/notification_guts_conversation_header_height" @@ -41,16 +41,20 @@ android:layout_height="@dimen/notification_guts_conversation_icon_size" android:layout_centerVertical="true" android:layout_alignParentStart="true" - android:layout_marginEnd="6dp" /> + android:layout_marginEnd="15dp" /> <LinearLayout android:id="@+id/names" + android:layout_weight="1" + android:layout_width="0dp" android:orientation="vertical" - android:layout_width="wrap_content" + android:layout_height="wrap_content" android:minHeight="@dimen/notification_guts_conversation_icon_size" android:layout_centerVertical="true" android:gravity="center_vertical" - android:layout_toEndOf="@id/conversation_icon"> + android:layout_alignEnd="@id/conversation_icon" + android:layout_toEndOf="@id/conversation_icon" + android:layout_alignStart="@id/mute"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" @@ -107,67 +111,40 @@ android:layout_weight="1" style="@style/TextAppearance.NotificationImportanceChannel"/> </LinearLayout> + <TextView + android:id="@+id/delegate_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:ellipsize="end" + android:text="@string/notification_delegate_header" + android:layout_toEndOf="@id/pkg_divider" + android:maxLines="1" /> </LinearLayout> - <TextView - android:id="@+id/pkg_divider" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - style="@style/TextAppearance.NotificationImportanceHeader" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:layout_toEndOf="@id/name" - android:text="@*android:string/notification_header_divider_symbol" /> - <TextView - android:id="@+id/delegate_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - style="@style/TextAppearance.NotificationImportanceHeader" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:ellipsize="end" - android:text="@string/notification_delegate_header" - android:layout_toEndOf="@id/pkg_divider" - android:maxLines="1" /> - <!-- end aligned fields --> <ImageButton - android:id="@+id/demote" - android:layout_width="@dimen/notification_importance_toggle_size" - android:layout_height="@dimen/notification_importance_toggle_size" - android:layout_centerVertical="true" - android:background="@drawable/ripple_drawable" - android:contentDescription="@string/demote" - android:src="@drawable/ic_demote_conversation" - android:layout_toStartOf="@id/app_settings" - android:tint="@color/notification_guts_link_icon_tint"/> - <!-- Optional link to app. Only appears if the channel is not disabled and the app -asked for it --> - <ImageButton - android:id="@+id/app_settings" + android:id="@+id/mute" android:layout_width="@dimen/notification_importance_toggle_size" android:layout_height="@dimen/notification_importance_toggle_size" android:layout_centerVertical="true" - android:visibility="gone" android:background="@drawable/ripple_drawable" - android:contentDescription="@string/notification_app_settings" - android:src="@drawable/ic_info" - android:layout_toStartOf="@id/info" + android:layout_toStartOf="@id/fave" android:tint="@color/notification_guts_link_icon_tint"/> <ImageButton - android:id="@+id/info" + android:id="@+id/fave" android:layout_width="@dimen/notification_importance_toggle_size" android:layout_height="@dimen/notification_importance_toggle_size" android:layout_centerVertical="true" android:background="@drawable/ripple_drawable" - android:contentDescription="@string/notification_more_settings" - android:src="@drawable/ic_settings" android:layout_alignParentEnd="true" android:tint="@color/notification_guts_link_icon_tint"/> - </RelativeLayout> + + </LinearLayout> <LinearLayout android:id="@+id/actions" @@ -182,29 +159,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> - <Button - android:id="@+id/bubble" - android:layout_height="@dimen/notification_guts_conversation_action_height" - android:layout_width="match_parent" - style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_favorite" - android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_create_bubble" - android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" - android:drawableTint="@color/notification_guts_link_icon_tint"/> - <View - android:layout_width="match_parent" - android:layout_height="0.5dp" - android:background="@color/GM2_grey_300" /> <Button - android:id="@+id/home" + android:id="@+id/snooze" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_home_screen" + android:text="@string/notification_menu_snooze_action" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_add_to_home" + android:drawableStart="@drawable/ic_snooze" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -212,12 +175,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> + <Button - android:id="@+id/fave" + android:id="@+id/bubble" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" + android:text="@string/notification_conversation_favorite" android:gravity="left|center_vertical" + android:drawableStart="@drawable/ic_create_bubble" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -226,13 +192,13 @@ asked for it --> android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> <Button - android:id="@+id/snooze" + android:id="@+id/home" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_menu_snooze_action" + android:text="@string/notification_conversation_home_screen" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_snooze" + android:drawableStart="@drawable/ic_add_to_home" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -240,14 +206,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> + <Button - android:id="@+id/mute" + android:id="@+id/info" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_mute" + android:drawableStart="@drawable/ic_settings" + android:text="@string/notification_menu_settings_action" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_notifications_silence" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index d72ce5eea78d..8de2df592eda 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -487,9 +487,6 @@ <!-- Whether or not to add a "people" notifications section --> <bool name="config_usePeopleFiltering">false</bool> - <!-- Package name for controls plugin --> - <string name="config_controlsPluginPackageName" translatable="false">com.android.systemui.controls.panel</string> - <!-- Defines the blacklist for system icons. That is to say, the icons in the status bar that are part of the blacklist are never displayed. Each item in the blacklist must be a string defined in core/res/res/config.xml to properly blacklist the icon. diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c4fa4e5d4ef3..0db7d67efd8d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -103,7 +103,8 @@ <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item> <dimen name="group_overflow_number_size">@*android:dimen/notification_text_size</dimen> - <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end</dimen> + <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end + </dimen> <!-- max height of a notification such that the content can still fade out when closing --> <dimen name="max_notification_fadeout_height">100dp</dimen> @@ -267,7 +268,9 @@ <dimen name="status_bar_icon_drawing_size">15dp</dimen> <!-- size at which Notification icons will be drawn on Ambient Display --> - <dimen name="status_bar_icon_drawing_size_dark">@*android:dimen/notification_header_icon_size_ambient</dimen> + <dimen name="status_bar_icon_drawing_size_dark"> + @*android:dimen/notification_header_icon_size_ambient + </dimen> <!-- size of notification icons when the notifications are hidden --> <dimen name="hidden_shelf_icon_size">16dp</dimen> @@ -299,8 +302,11 @@ <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen> <dimen name="global_screenshot_bg_padding">20dp</dimen> <dimen name="global_screenshot_x_scale">80dp</dimen> + <dimen name="screenshot_preview_elevation">8dp</dimen> <dimen name="screenshot_offset_y">48dp</dimen> <dimen name="screenshot_offset_x">16dp</dimen> + <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen> + <dimen name="screenshot_dismiss_button_margin">8dp</dimen> <dimen name="screenshot_action_container_offset_y">32dp</dimen> <dimen name="screenshot_action_container_corner_radius">10dp</dimen> <dimen name="screenshot_action_container_padding_vertical">10dp</dimen> @@ -597,10 +603,14 @@ <!-- The height of the divider between the individual notifications in a notification group. --> - <dimen name="notification_children_container_divider_height">@dimen/notification_divider_height</dimen> + <dimen name="notification_children_container_divider_height"> + @dimen/notification_divider_height + </dimen> <!-- The top margin for the notification children container in its non-expanded form. --> - <dimen name="notification_children_container_margin_top">@*android:dimen/notification_content_margin_top</dimen> + <dimen name="notification_children_container_margin_top"> + @*android:dimen/notification_content_margin_top + </dimen> <!-- The height of a notification header --> <dimen name="notification_header_height">53dp</dimen> @@ -1061,9 +1071,12 @@ <integer name="wireless_charging_scale_dots_duration">83</integer> <integer name="wireless_charging_num_dots">16</integer> <!-- Starting text size in sp of batteryLevel for wireless charging animation --> - <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">0</item> + <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen"> + 0 + </item> <!-- Ending text size in sp of batteryLevel for wireless charging animation --> - <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24</item> + <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24 + </item> <!-- time until battery info is at full opacity--> <integer name="wireless_charging_anim_opacity_offset">80</integer> <!-- duration batteryLevel opacity goes from 0 to 1 duration --> @@ -1090,7 +1103,7 @@ <!-- Blur radius on status bar window and power menu --> <dimen name="min_window_blur_radius">1px</dimen> - <dimen name="max_window_blur_radius">250px</dimen> + <dimen name="max_window_blur_radius">150px</dimen> <!-- How much into a DisplayCutout's bounds we can go, on each side --> <dimen name="display_cutout_margin_consumption">0px</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index b85b51e4f2cc..ebaf55fd7e9c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1829,23 +1829,23 @@ <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification --> <string name="demote">Mark this notification as not a conversation</string> - <!-- [CHAR LIMIT=100] Mark this conversation as a favorite --> - <string name="notification_conversation_favorite">Mark as important</string> + <!-- [CHAR LIMIT=100] This conversation is marked as important --> + <string name="notification_conversation_favorite">Important conversation</string> - <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite --> - <string name="notification_conversation_unfavorite">Mark as unimportant</string> + <!-- [CHAR LIMIT=100] This conversation is not marked as important --> + <string name="notification_conversation_unfavorite">Not an important conversation</string> - <!-- [CHAR LIMIT=100] Mute this conversation --> - <string name="notification_conversation_mute">Silence</string> + <!-- [CHAR LIMIT=100] This conversation is silenced (will not make sound or vibrate)--> + <string name="notification_conversation_mute">Silenced</string> - <!-- [CHAR LIMIT=100] Umute this conversation --> + <!-- [CHAR LIMIT=100] This conversation is alerting (may make sound and/or vibrate)--> <string name="notification_conversation_unmute">Alerting</string> <!-- [CHAR LIMIT=100] Show notification as bubble --> - <string name="notification_conversation_bubble">Show as bubble</string> + <string name="notification_conversation_bubble">Show bubble</string> <!-- [CHAR LIMIT=100] Turn off bubbles for notification --> - <string name="notification_conversation_unbubble">Turn off bubbles</string> + <string name="notification_conversation_unbubble">Remove bubbles</string> <!-- [CHAR LIMIT=100] Add this conversation to home screen --> <string name="notification_conversation_home_screen">Add to home screen</string> @@ -1860,7 +1860,10 @@ <string name="notification_menu_snooze_description">notification snooze options</string> <!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] --> - <string name="notification_menu_snooze_action">Snooze</string> + <string name="notification_menu_snooze_action">Remind me</string> + + <!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] --> + <string name="notification_menu_settings_action">Settings</string> <!-- Notification: Snooze panel: Snooze undo button label. [CHAR LIMIT=50]--> <string name="snooze_undo">UNDO</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java index a37861068e48..b8997c29dd52 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstanceManager.java @@ -135,8 +135,7 @@ public class PluginInstanceManager<T extends Plugin> { ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); for (PluginInfo info : plugins) { if (className.startsWith(info.mPackage)) { - disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); - disableAny = true; + disableAny |= disable(info, PluginEnabler.DISABLED_FROM_EXPLICIT_CRASH); } } return disableAny; @@ -144,10 +143,11 @@ public class PluginInstanceManager<T extends Plugin> { public boolean disableAll() { ArrayList<PluginInfo> plugins = new ArrayList<PluginInfo>(mPluginHandler.mPlugins); + boolean disabledAny = false; for (int i = 0; i < plugins.size(); i++) { - disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); + disabledAny |= disable(plugins.get(i), PluginEnabler.DISABLED_FROM_SYSTEM_CRASH); } - return plugins.size() != 0; + return disabledAny; } private boolean isPluginWhitelisted(ComponentName pluginName) { @@ -166,7 +166,7 @@ public class PluginInstanceManager<T extends Plugin> { return false; } - private void disable(PluginInfo info, @PluginEnabler.DisableReason int reason) { + private boolean disable(PluginInfo info, @PluginEnabler.DisableReason int reason) { // Live by the sword, die by the sword. // Misbehaving plugins get disabled and won't come back until uninstall/reinstall. @@ -176,10 +176,12 @@ public class PluginInstanceManager<T extends Plugin> { // assuming one of them must be bad. if (isPluginWhitelisted(pluginComponent)) { // Don't disable whitelisted plugins as they are a part of the OS. - return; + return false; } Log.w(TAG, "Disabling plugin " + pluginComponent.flattenToShortString()); mManager.getPluginEnabler().setDisabled(pluginComponent, reason); + + return true; } public <T> boolean dependsOn(Plugin p, Class<T> cls) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index f61f585cfe7c..f48210cf90c4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -42,7 +42,6 @@ import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.animation.Animation; -import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; @@ -251,9 +250,9 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe SliceItem item = rc.getSliceItem(); final Uri itemTag = item.getSlice().getUri(); // Try to reuse the view if already exists in the layout - KeyguardSliceButton button = mRow.findViewWithTag(itemTag); + KeyguardSliceTextView button = mRow.findViewWithTag(itemTag); if (button == null) { - button = new KeyguardSliceButton(mContext); + button = new KeyguardSliceTextView(mContext); button.setTextColor(blendedColor); button.setTag(itemTag); final int viewIndex = i - (mHasHeader ? 1 : 0); @@ -316,8 +315,8 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe int childCount = mRow.getChildCount(); for (int i = 0; i < childCount; i++) { View v = mRow.getChildAt(i); - if (v instanceof Button) { - ((Button) v).setTextColor(blendedColor); + if (v instanceof TextView) { + ((TextView) v).setTextColor(blendedColor); } } } @@ -500,8 +499,8 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); - if (child instanceof KeyguardSliceButton) { - ((KeyguardSliceButton) child).setMaxWidth(width / childCount); + if (child instanceof KeyguardSliceTextView) { + ((KeyguardSliceTextView) child).setMaxWidth(width / childCount); } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -527,13 +526,13 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe * Representation of an item that appears under the clock on main keyguard message. */ @VisibleForTesting - static class KeyguardSliceButton extends Button implements + static class KeyguardSliceTextView extends TextView implements ConfigurationController.ConfigurationListener { @StyleRes private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary; - public KeyguardSliceButton(Context context) { + KeyguardSliceTextView(Context context) { super(context, null /* attrs */, 0 /* styleAttr */, sStyleId); onDensityOrFontScaleChanged(); setEllipsize(TruncateAt.END); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index bea55c820b40..2d55a1ddf654 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -146,7 +146,6 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V int viewType) { BadgedImageView view = (BadgedImageView) LayoutInflater.from(parent.getContext()) .inflate(R.layout.bubble_view, parent, false); - view.setPadding(15, 15, 15, 15); return new ViewHolder(view); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt index 53841e2f144b..49a16d892ef4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt @@ -20,6 +20,6 @@ import android.service.controls.Control data class ControlStatus( val control: Control, - val favorite: Boolean, + var favorite: Boolean, val removed: Boolean = false )
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index b3ba2b22f6df..fce504120b62 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -26,10 +26,18 @@ interface ControlsController : UserAwareController { val available: Boolean fun getFavoriteControls(): List<ControlInfo> - fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit) + fun loadForComponent( + componentName: ComponentName, + callback: (List<ControlStatus>, List<String>) -> Unit + ) + fun subscribeToFavorites() fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) - fun countFavoritesForComponent(componentName: ComponentName): Int = 0 + fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>) + + fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo> + fun countFavoritesForComponent(componentName: ComponentName): Int + fun unsubscribe() fun action(controlInfo: ControlInfo, action: ControlAction) fun refreshStatus(componentName: ComponentName, control: Control) diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 7de1557ebc65..e611197b78ae 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -70,9 +70,10 @@ class ControlsControllerImpl @Inject constructor ( } // Map of map: ComponentName -> (String -> ControlInfo). - // Only for current user + // @GuardedBy("currentFavorites") - private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() + private val currentFavorites = ArrayMap<ComponentName, MutableList<ControlInfo>>() + .withDefault { mutableListOf() } private var userChanging: Boolean = true @@ -180,15 +181,14 @@ class ControlsControllerImpl @Inject constructor ( val infos = persistenceWrapper.readFavorites() synchronized(currentFavorites) { infos.forEach { - currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() }) - .put(it.controlId, it) + currentFavorites.getOrPut(it.component, { mutableListOf() }).add(it) } } } override fun loadForComponent( componentName: ComponentName, - callback: (List<ControlStatus>) -> Unit + callback: (List<ControlStatus>, List<String>) -> Unit ) { if (!confirmAvailability()) { if (userChanging) { @@ -200,29 +200,34 @@ class ControlsControllerImpl @Inject constructor ( TimeUnit.MILLISECONDS ) } else { - callback(emptyList()) + callback(emptyList(), emptyList()) } return } bindingController.bindAndLoad(componentName) { synchronized(currentFavorites) { - val favoritesForComponentKeys: Set<String> = - currentFavorites.get(componentName)?.keys ?: emptySet() - val changed = updateFavoritesLocked(componentName, it) + val favoritesForComponentKeys: List<String> = + currentFavorites.getValue(componentName).map { it.controlId } + val changed = updateFavoritesLocked(componentName, it, favoritesForComponentKeys) if (changed) { persistenceWrapper.storeFavorites(favoritesAsListLocked()) } - val removed = findRemovedLocked(favoritesForComponentKeys, it) - callback(removed.map { currentFavorites.getValue(componentName).getValue(it) } - .map(::createRemovedStatus) + - it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) }) + val removed = findRemovedLocked(favoritesForComponentKeys.toSet(), it) + val controlsWithFavorite = + it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) } + callback( + currentFavorites.getValue(componentName) + .filter { it.controlId in removed } + .map(::createRemovedStatus) + controlsWithFavorite, + favoritesForComponentKeys + ) } } } private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus { val intent = Intent(context, ControlsFavoritingActivity::class.java).apply { - putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component) + putExtra(Intent.EXTRA_COMPONENT_NAME, controlInfo.component) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP } val pendingIntent = PendingIntent.getActivity(context, @@ -243,17 +248,24 @@ class ControlsControllerImpl @Inject constructor ( } @GuardedBy("currentFavorites") - private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean { - val favorites = currentFavorites.get(componentName) ?: mutableMapOf() - val favoriteKeys = favorites.keys + private fun updateFavoritesLocked( + componentName: ComponentName, + list: List<Control>, + favoriteKeys: List<String> + ): Boolean { + val favorites = currentFavorites.get(componentName) ?: mutableListOf() if (favoriteKeys.isEmpty()) return false // early return var changed = false - list.forEach { - if (it.controlId in favoriteKeys) { - val value = favorites.getValue(it.controlId) - if (value.controlTitle != it.title || value.deviceType != it.deviceType) { - favorites[it.controlId] = value.copy(controlTitle = it.title, - deviceType = it.deviceType) + list.forEach { control -> + if (control.controlId in favoriteKeys) { + val index = favorites.indexOfFirst { it.controlId == control.controlId } + val value = favorites[index] + if (value.controlTitle != control.title || + value.deviceType != control.deviceType) { + favorites[index] = value.copy( + controlTitle = control.title, + deviceType = control.deviceType + ) changed = true } } @@ -263,14 +275,14 @@ class ControlsControllerImpl @Inject constructor ( @GuardedBy("currentFavorites") private fun favoritesAsListLocked(): List<ControlInfo> { - return currentFavorites.flatMap { it.value.values } + return currentFavorites.flatMap { it.value } } override fun subscribeToFavorites() { if (!confirmAvailability()) return // Make a copy of the favorites list val favorites = synchronized(currentFavorites) { - currentFavorites.flatMap { it.value.values.toList() } + currentFavorites.flatMap { it.value } } bindingController.subscribe(favorites) } @@ -286,22 +298,19 @@ class ControlsControllerImpl @Inject constructor ( val listOfControls = synchronized(currentFavorites) { if (state) { if (controlInfo.component !in currentFavorites) { - currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>()) + currentFavorites.put(controlInfo.component, mutableListOf()) changed = true } val controlsForComponent = currentFavorites.getValue(controlInfo.component) - if (controlInfo.controlId !in controlsForComponent) { - controlsForComponent.put(controlInfo.controlId, controlInfo) + if (controlsForComponent.firstOrNull { + it.controlId == controlInfo.controlId + } == null) { + controlsForComponent.add(controlInfo) changed = true - } else { - if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) { - controlsForComponent.put(controlInfo.controlId, controlInfo) - changed = true - } } } else { changed = currentFavorites.get(controlInfo.component) - ?.remove(controlInfo.controlId) != null + ?.remove(controlInfo) != null } favoritesAsListLocked() } @@ -310,6 +319,19 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun replaceFavoritesForComponent( + componentName: ComponentName, + favorites: List<ControlInfo> + ) { + if (!confirmAvailability()) return + val filtered = favorites.filter { it.component == componentName } + val listOfControls = synchronized(currentFavorites) { + currentFavorites.put(componentName, filtered.toMutableList()) + favoritesAsListLocked() + } + persistenceWrapper.storeFavorites(listOfControls) + } + override fun refreshStatus(componentName: ComponentName, control: Control) { if (!confirmAvailability()) { Log.d(TAG, "Controls not available") @@ -317,7 +339,13 @@ class ControlsControllerImpl @Inject constructor ( } executor.execute { synchronized(currentFavorites) { - val changed = updateFavoritesLocked(componentName, listOf(control)) + val favoriteKeysForComponent = + currentFavorites.get(componentName)?.map { it.controlId } ?: emptyList() + val changed = updateFavoritesLocked( + componentName, + listOf(control), + favoriteKeysForComponent + ) if (changed) { persistenceWrapper.storeFavorites(favoritesAsListLocked()) } @@ -361,6 +389,12 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo> { + return synchronized(currentFavorites) { + currentFavorites.get(componentName) ?: emptyList() + } + } + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("ControlsController state:") pw.println(" Available: $available") @@ -368,10 +402,8 @@ class ControlsControllerImpl @Inject constructor ( pw.println(" Current user: ${currentUser.identifier}") pw.println(" Favorites:") synchronized(currentFavorites) { - currentFavorites.forEach { - it.value.forEach { - pw.println(" ${it.value}") - } + favoritesAsListLocked().forEach { + pw.println(" ${ it }") } } pw.println(bindingController.toString()) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt index b12243964fc1..89caaceebb5c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt @@ -29,6 +29,7 @@ import androidx.recyclerview.widget.RecyclerView import com.android.settingslib.applications.DefaultAppInfo import com.android.settingslib.widget.CandidateInfo import com.android.systemui.R +import java.text.Collator import java.util.concurrent.Executor /** @@ -44,23 +45,27 @@ import java.util.concurrent.Executor * @param onAppSelected a callback to indicate that an app has been selected in the list. */ class AppAdapter( + backgroundExecutor: Executor, uiExecutor: Executor, lifecycle: Lifecycle, controlsListingController: ControlsListingController, private val layoutInflater: LayoutInflater, private val onAppSelected: (ComponentName?) -> Unit = {}, - private val favoritesRenderer: FavoritesRenderer + private val favoritesRenderer: FavoritesRenderer, + private val resources: Resources ) : RecyclerView.Adapter<AppAdapter.Holder>() { private var listOfServices = emptyList<CandidateInfo>() private val callback = object : ControlsListingController.ControlsListingCallback { override fun onServicesUpdated(list: List<CandidateInfo>) { - uiExecutor.execute { - listOfServices = list.sortedBy { - it.loadLabel().toString() + backgroundExecutor.execute { + val collator = Collator.getInstance(resources.getConfiguration().locale) + val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) { + it.loadLabel() } - notifyDataSetChanged() + listOfServices = list.sortedWith(localeComparator) + uiExecutor.execute(::notifyDataSetChanged) } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 65dcc2b193d5..d3cabe67790e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -25,101 +25,150 @@ import android.view.ViewGroup import android.widget.CheckBox import android.widget.ImageView import android.widget.TextView +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R -import com.android.systemui.controls.ControlStatus -import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.ui.RenderInfo +private typealias ModelFavoriteChanger = (String, Boolean) -> Unit + /** * Adapter for binding [Control] information to views. * + * The model for this adapter is provided by a [FavoriteModel] that is set using + * [changeFavoritesModel]. This allows for updating the model if there's a reload. + * * @param layoutInflater an inflater for the views in the containing [RecyclerView] - * @param favoriteCallback a callback to be called when the favorite status of a [Control] is - * changed. The callback will take a [ControlInfo.Builder] that's - * pre-populated with the [Control] information and the new favorite - * status. + * @param onlyFavorites set to true to only display favorites instead of all controls */ class ControlAdapter( private val layoutInflater: LayoutInflater, - private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit -) : RecyclerView.Adapter<ControlAdapter.Holder>() { + private val onlyFavorites: Boolean = false +) : RecyclerView.Adapter<Holder>() { + + companion object { + private const val TYPE_ZONE = 0 + private const val TYPE_CONTROL = 1 + } + + val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (getItemViewType(position) == TYPE_ZONE) 2 else 1 + } + } - var listOfControls = emptyList<ControlStatus>() + var modelList: List<ElementWrapper> = emptyList() + private var favoritesModel: FavoriteModel? = null - override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder { - return Holder(layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply { - layoutParams.apply { - width = ViewGroup.LayoutParams.MATCH_PARENT + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { + return when (viewType) { + TYPE_CONTROL -> { + ControlHolder( + layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply { + layoutParams.apply { + width = ViewGroup.LayoutParams.MATCH_PARENT + } + elevation = 15f + }, + { id, favorite -> + favoritesModel?.changeFavoriteStatus(id, favorite) + }) + } + TYPE_ZONE -> { + ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false)) } - elevation = 15f - }) + else -> throw IllegalStateException("Wrong viewType: $viewType") + } } - override fun getItemCount() = listOfControls.size + fun changeFavoritesModel(favoritesModel: FavoriteModel) { + this.favoritesModel = favoritesModel + if (onlyFavorites) { + modelList = favoritesModel.favorites + } else { + modelList = favoritesModel.all + } + notifyDataSetChanged() + } + + override fun getItemCount() = modelList.size override fun onBindViewHolder(holder: Holder, index: Int) { - holder.bindData(listOfControls[index], favoriteCallback) + holder.bindData(modelList[index]) } + override fun getItemViewType(position: Int): Int { + return when (modelList[position]) { + is ZoneNameWrapper -> TYPE_ZONE + is ControlWrapper -> TYPE_CONTROL + } + } +} + +/** + * Holder for binding views in the [RecyclerView]- + * @param view the [View] for this [Holder] + */ +sealed class Holder(view: View) : RecyclerView.ViewHolder(view) { + /** - * Holder for binding views in the [RecyclerView]- + * Bind the data from the model into the view */ - class Holder(view: View) : RecyclerView.ViewHolder(view) { - private val icon: ImageView = itemView.requireViewById(R.id.icon) - private val title: TextView = itemView.requireViewById(R.id.title) - private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) - private val removed: TextView = itemView.requireViewById(R.id.status) - private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply { - visibility = View.VISIBLE - } + abstract fun bindData(wrapper: ElementWrapper) +} - /** - * Bind data to the view - * @param data information about the [Control] - * @param callback a callback to be called when the favorite status of the [Control] is - * changed. The callback will take a [ControlInfo.Builder] that's - * pre-populated with the [Control] information and the new favorite status. - */ - fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) { - val renderInfo = getRenderInfo(data.control.deviceType, data.favorite) - title.text = data.control.title - subtitle.text = data.control.subtitle - favorite.isChecked = data.favorite - removed.text = if (data.removed) "Removed" else "" - favorite.setOnClickListener { - val infoBuilder = ControlInfo.Builder().apply { - controlId = data.control.controlId - controlTitle = data.control.title - deviceType = data.control.deviceType - } - callback(infoBuilder, favorite.isChecked) - } - itemView.setOnClickListener { - favorite.performClick() - } - applyRenderInfo(renderInfo) - } +/** + * Holder for using with [ZoneNameWrapper] to display names of zones. + */ +private class ZoneHolder(view: View) : Holder(view) { + private val zone: TextView = itemView as TextView - private fun getRenderInfo( - @DeviceTypes.DeviceType deviceType: Int, - favorite: Boolean - ): RenderInfo { - return RenderInfo.lookup(deviceType, favorite) - } + override fun bindData(wrapper: ElementWrapper) { + wrapper as ZoneNameWrapper + zone.text = wrapper.zoneName + } +} - private fun applyRenderInfo(ri: RenderInfo) { - val context = itemView.context - val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) +/** + * Holder for using with [ControlWrapper] to display names of zones. + * @param favoriteCallback this callback will be called whenever the favorite state of the + * [Control] this view represents changes. + */ +private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChanger) : Holder(view) { + private val icon: ImageView = itemView.requireViewById(R.id.icon) + private val title: TextView = itemView.requireViewById(R.id.title) + private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) + private val removed: TextView = itemView.requireViewById(R.id.status) + private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply { + visibility = View.VISIBLE + } - icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId)) - icon.setImageTintList(fg) + override fun bindData(wrapper: ElementWrapper) { + wrapper as ControlWrapper + val data = wrapper.controlStatus + val renderInfo = getRenderInfo(data.control.deviceType) + title.text = data.control.title + subtitle.text = data.control.subtitle + favorite.isChecked = data.favorite + removed.text = if (data.removed) "Removed" else "" + favorite.setOnClickListener { + favoriteCallback(data.control.controlId, favorite.isChecked) } + applyRenderInfo(renderInfo) } - fun setItems(list: List<ControlStatus>) { - listOfControls = list - notifyDataSetChanged() + private fun getRenderInfo( + @DeviceTypes.DeviceType deviceType: Int + ): RenderInfo { + return RenderInfo.lookup(deviceType, true) + } + + private fun applyRenderInfo(ri: RenderInfo) { + val context = itemView.context + val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) + + icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId)) + icon.setImageTintList(fg) } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index be5258344492..1e5237148188 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -18,10 +18,14 @@ package com.android.systemui.controls.management import android.app.Activity import android.content.ComponentName +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater +import android.view.ViewStub +import android.widget.Button import android.widget.TextView import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher @@ -41,13 +45,36 @@ class ControlsFavoritingActivity @Inject constructor( companion object { private const val TAG = "ControlsFavoritingActivity" const val EXTRA_APP = "extra_app_label" - const val EXTRA_COMPONENT = "extra_component" } - private lateinit var recyclerView: RecyclerView - private lateinit var adapter: ControlAdapter + private lateinit var recyclerViewAll: RecyclerView + private lateinit var adapterAll: ControlAdapter + private lateinit var recyclerViewFavorites: RecyclerView + private lateinit var adapterFavorites: ControlAdapter private var component: ComponentName? = null + private var currentModel: FavoriteModel? = null + private var itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback( + /* dragDirs */ ItemTouchHelper.UP + or ItemTouchHelper.DOWN + or ItemTouchHelper.LEFT + or ItemTouchHelper.RIGHT, + /* swipeDirs */0 + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return currentModel?.onMoveItem( + viewHolder.adapterPosition, target.adapterPosition) != null + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + override fun isItemViewSwipeEnabled() = false + } + private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = controller.currentUserId @@ -62,41 +89,77 @@ class ControlsFavoritingActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.controls_management) + requireViewById<ViewStub>(R.id.stub).apply { + layoutResource = R.layout.controls_management_favorites + inflate() + } val app = intent.getCharSequenceExtra(EXTRA_APP) - component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT) - - // If we have no component name, there's not much we can do. - val callback = component?.let { - { infoBuilder: ControlInfo.Builder, status: Boolean -> - infoBuilder.componentName = it - controller.changeFavoriteStatus(infoBuilder.build(), status) - } - } ?: { _, _ -> Unit } + component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) - recyclerView = requireViewById(R.id.list) - adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback) - recyclerView.adapter = adapter - recyclerView.layoutManager = GridLayoutManager(applicationContext, 2) - val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin) - recyclerView.addItemDecoration(MarginItemDecorator(margin, margin)) + setUpRecyclerViews() requireViewById<TextView>(R.id.title).text = app?.let { it } ?: resources.getText(R.string.controls_favorite_default_title) requireViewById<TextView>(R.id.subtitle).text = resources.getText(R.string.controls_favorite_subtitle) - currentUserTracker.startTracking() - } + requireViewById<Button>(R.id.done).setOnClickListener { + if (component == null) return@setOnClickListener + val favoritesForStorage = currentModel?.favorites?.map { + with(it.controlStatus.control) { + ControlInfo(component!!, controlId, title, deviceType) + } + } + if (favoritesForStorage != null) { + controller.replaceFavoritesForComponent(component!!, favoritesForStorage) + finishAffinity() + } + } - override fun onResume() { - super.onResume() component?.let { - controller.loadForComponent(it) { + controller.loadForComponent(it) { allControls, favoriteKeys -> executor.execute { - adapter.setItems(it) + val favoriteModel = FavoriteModel( + allControls, + favoriteKeys, + allAdapter = adapterAll, + favoritesAdapter = adapterFavorites) + adapterAll.changeFavoritesModel(favoriteModel) + adapterFavorites.changeFavoritesModel(favoriteModel) + currentModel = favoriteModel } } } + + currentUserTracker.startTracking() + } + + private fun setUpRecyclerViews() { + val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin) + val itemDecorator = MarginItemDecorator(margin, margin) + val layoutInflater = LayoutInflater.from(applicationContext) + + adapterAll = ControlAdapter(layoutInflater) + recyclerViewAll = requireViewById<RecyclerView>(R.id.listAll).apply { + adapter = adapterAll + layoutManager = GridLayoutManager(applicationContext, 2).apply { + spanSizeLookup = adapterAll.spanSizeLookup + } + addItemDecoration(itemDecorator) + } + + adapterFavorites = ControlAdapter(layoutInflater, true) + recyclerViewFavorites = requireViewById<RecyclerView>(R.id.listFavorites).apply { + layoutManager = GridLayoutManager(applicationContext, 2) + adapter = adapterFavorites + addItemDecoration(itemDecorator) + } + ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerViewFavorites) + } + + override fun onDestroy() { + currentUserTracker.stopTracking() + super.onDestroy() } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 645e929d6a10..ad4bdefdff3e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -20,6 +20,7 @@ import android.content.ComponentName import android.content.Intent import android.os.Bundle import android.view.LayoutInflater +import android.view.ViewStub import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -63,11 +64,21 @@ class ControlsProviderSelectorActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.controls_management) + requireViewById<ViewStub>(R.id.stub).apply { + layoutResource = R.layout.controls_management_apps + inflate() + } recyclerView = requireViewById(R.id.list) - recyclerView.adapter = AppAdapter(executor, lifecycle, listingController, - LayoutInflater.from(this), ::launchFavoritingActivity, - FavoritesRenderer(resources, controlsController::countFavoritesForComponent)) + recyclerView.adapter = AppAdapter( + backExecutor, + executor, + lifecycle, + listingController, + LayoutInflater.from(this), + ::launchFavoritingActivity, + FavoritesRenderer(resources, controlsController::countFavoritesForComponent), + resources) recyclerView.layoutManager = LinearLayoutManager(applicationContext) requireViewById<TextView>(R.id.title).text = @@ -89,11 +100,16 @@ class ControlsProviderSelectorActivity @Inject constructor( .apply { putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it)) - putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + putExtra(Intent.EXTRA_COMPONENT_NAME, it) + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP } startActivity(intent) } } } + + override fun onDestroy() { + currentUserTracker.stopTracking() + super.onDestroy() + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt new file mode 100644 index 000000000000..6bade0aeb998 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt @@ -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.systemui.controls.management + +import android.text.TextUtils +import android.util.Log +import com.android.systemui.controls.ControlStatus +import java.util.Collections +import java.util.Comparator + +/** + * Model for keeping track of current favorites and their order. + * + * This model is to be used with two [ControlAdapter] one that shows only favorites in the current + * order and another that shows all controls, separated by zone. When the favorite state of any + * control is modified or when the favorites are reordered, the adapters are notified of the change. + * + * @param listControls list of all the [ControlStatus] to display. This includes controls currently + * marked as favorites as well as those that have been removed (not returned + * from load) + * @param listFavoritesIds list of the [Control.controlId] for all the favorites, including those + * that have been removed. + * @param favoritesAdapter [ControlAdapter] used by the [RecyclerView] that shows only favorites + * @param allAdapter [ControlAdapter] used by the [RecyclerView] that shows all controls + */ +class FavoriteModel( + private val listControls: List<ControlStatus>, + listFavoritesIds: List<String>, + private val favoritesAdapter: ControlAdapter, + private val allAdapter: ControlAdapter +) { + + companion object { + private const val TAG = "FavoriteModel" + } + + /** + * List of favorite controls ([ControlWrapper]) in order. + * + * Initially, this list will give a list of wrappers in the order specified by the constructor + * variable `listFavoriteIds`. + * + * As the favorites are added, removed or moved, this list will keep track of those changes. + */ + val favorites: List<ControlWrapper> = listFavoritesIds.map { id -> + ControlWrapper(listControls.first { it.control.controlId == id }) + }.toMutableList() + + /** + * List of all controls by zones. + * + * Lists all the controls with the zone names interleaved as a flat list. After each zone name, + * the controls in that zone are listed. Zones are listed in alphabetical order + */ + val all: List<ElementWrapper> = listControls.groupBy { it.control.zone } + .mapKeys { it.key ?: "" } // map null to empty + .toSortedMap(CharSequenceComparator()) + .flatMap { + val controls = it.value.map { ControlWrapper(it) } + if (!TextUtils.isEmpty(it.key)) { + listOf(ZoneNameWrapper(it.key)) + controls + } else { + controls + } + } + + /** + * Change the favorite status of a [Control]. + * + * This can be invoked from any of the [ControlAdapter]. It will change the status of that + * control and either add it to the list of favorites (at the end) or remove it from it. + * + * Removing the favorite status from a Removed control will make it disappear completely if + * changes are saved. + * + * @param controlId the id of the [Control] to change the status + * @param favorite `true` if and only if it's set to be a favorite. + */ + fun changeFavoriteStatus(controlId: String, favorite: Boolean) { + favorites as MutableList + val index = all.indexOfFirst { + it is ControlWrapper && it.controlStatus.control.controlId == controlId + } + val control = (all[index] as ControlWrapper).controlStatus + if (control.favorite == favorite) { + Log.d(TAG, "Changing favorite to same state for ${control.control.controlId} ") + return + } else { + control.favorite = favorite + } + allAdapter.notifyItemChanged(index) + if (favorite) { + favorites.add(all[index] as ControlWrapper) + favoritesAdapter.notifyItemInserted(favorites.size - 1) + } else { + val i = favorites.indexOfFirst { it.controlStatus.control.controlId == controlId } + favorites.removeAt(i) + favoritesAdapter.notifyItemRemoved(i) + } + } + + /** + * Move items in the model and notify the [favoritesAdapter]. + */ + fun onMoveItem(from: Int, to: Int) { + if (from < to) { + for (i in from until to) { + Collections.swap(favorites, i, i + 1) + } + } else { + for (i in from downTo to + 1) { + Collections.swap(favorites, i, i - 1) + } + } + favoritesAdapter.notifyItemMoved(from, to) + } +} + +/** + * Compares [CharSequence] as [String]. + * + * It will have empty strings as the first element + */ +class CharSequenceComparator : Comparator<CharSequence> { + override fun compare(p0: CharSequence?, p1: CharSequence?): Int { + if (p0 == null && p1 == null) return 0 + else if (p0 == null && p1 != null) return -1 + else if (p0 != null && p1 == null) return 1 + return p0.toString().compareTo(p1.toString()) + } +} + +/** + * Wrapper classes for the different types of elements shown in the [RecyclerView]s in + * [ControlsFavoritingActivity]. + */ +sealed class ElementWrapper +data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper() +data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper()
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 53a23b89f943..0ec739fecaa7 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -30,6 +30,7 @@ import android.view.IWindowManager; import android.view.LayoutInflater; import com.android.internal.logging.MetricsLogger; +import com.android.internal.util.NotificationMessagingUtil; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.dagger.qualifiers.Background; @@ -191,6 +192,12 @@ public class DependencyProvider { return new AlwaysOnDisplayPolicy(context); } + /***/ + @Provides + public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) { + return new NotificationMessagingUtil(context); + } + /** */ @Provides public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 7b541991088c..f068d9c10e86 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.people.PeopleHubModule; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.phone.KeyguardLiftController; import com.android.systemui.statusbar.phone.StatusBar; @@ -68,7 +69,9 @@ import dagger.Provides; NotificationsModule.class, PeopleHubModule.class, }, - subcomponents = {StatusBarComponent.class, NotificationRowComponent.class}) + subcomponents = {StatusBarComponent.class, + NotificationRowComponent.class, + ExpandableNotificationRowComponent.class}) public abstract class SystemUIModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index b3fc027d1ac7..a3cd5fdd771b 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1045,7 +1045,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, Action action = getItem(position); View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); view.setOnClickListener(v -> onClickItem(position)); - view.setOnLongClickListener(v -> onLongClickItem(position)); + if (action instanceof LongPressAction) { + view.setOnLongClickListener(v -> onLongClickItem(position)); + } return view; } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index c9c38d31e865..146f36bcd04f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -36,8 +36,6 @@ import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.GlobalActionsPanelPlugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.ScrimController; @@ -49,8 +47,7 @@ import javax.inject.Inject; import dagger.Lazy; -public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, - PluginListener<GlobalActionsPanelPlugin> { +public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks { private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f; @@ -60,12 +57,9 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, private final DeviceProvisionedController mDeviceProvisionedController; private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension; private final BlurUtils mBlurUtils; - private GlobalActionsPanelPlugin mPlugin; private final CommandQueue mCommandQueue; private GlobalActionsDialog mGlobalActionsDialog; private boolean mDisabled; - private final PluginManager mPluginManager; - private final String mPluginPackageName; @Inject public GlobalActionsImpl(Context context, CommandQueue commandQueue, @@ -74,7 +68,6 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, mGlobalActionsDialogLazy = globalActionsDialogLazy; mKeyguardStateController = Dependency.get(KeyguardStateController.class); mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); - mPluginManager = Dependency.get(PluginManager.class); mCommandQueue = commandQueue; mBlurUtils = blurUtils; mCommandQueue.addCallback(this); @@ -82,17 +75,11 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, .newExtension(GlobalActionsPanelPlugin.class) .withPlugin(GlobalActionsPanelPlugin.class) .build(); - mPluginPackageName = mContext.getString( - com.android.systemui.R.string.config_controlsPluginPackageName); - mPluginManager.addPluginListener( - GlobalActionsPanelPlugin.ACTION, this, GlobalActionsPanelPlugin.class, true); } @Override public void destroy() { mCommandQueue.removeCallback(this); - mPluginManager.removePluginListener(this); - if (mPlugin != null) mPlugin.onDestroy(); if (mGlobalActionsDialog != null) { mGlobalActionsDialog.destroy(); mGlobalActionsDialog = null; @@ -105,7 +92,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, mGlobalActionsDialog = mGlobalActionsDialogLazy.get(); mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(), mDeviceProvisionedController.isDeviceProvisioned(), - mPlugin != null ? mPlugin : mPanelExtension.get()); + mPanelExtension.get()); Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth(); } @@ -205,16 +192,4 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks, mGlobalActionsDialog.dismissDialog(); } } - - @Override - public void onPluginConnected(GlobalActionsPanelPlugin plugin, Context pluginContext) { - if (pluginContext.getPackageName().equals(mPluginPackageName)) { - mPlugin = plugin; - } - } - - @Override - public void onPluginDisconnected(GlobalActionsPanelPlugin plugin) { - mPlugin = null; - } } diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java index 657a308224ae..11e215d0d358 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java @@ -153,11 +153,11 @@ public class EglHelper { return true; } - private boolean checkExtensionCapability(String extName) { + boolean checkExtensionCapability(String extName) { return mExts.contains(extName); } - private int getWcgCapability() { + int getWcgCapability() { if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) { return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; } @@ -212,7 +212,7 @@ public class EglHelper { if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) { attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE}; } - mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, attrs, 0); + mEglSurface = askCreatingEglWindowSurface(surfaceHolder, attrs, 0 /* offset */); } else { Log.w(TAG, "Create EglSurface failed: hasEglDisplay=" + hasEglDisplay() + ", has valid surface=" + surfaceHolder.getSurface().isValid()); @@ -235,6 +235,10 @@ public class EglHelper { return true; } + EGLSurface askCreatingEglWindowSurface(SurfaceHolder holder, int[] attrs, int offset) { + return eglCreateWindowSurface(mEglDisplay, mEglConfig, holder, attrs, offset); + } + /** * Destroy EglSurface. */ @@ -242,7 +246,7 @@ public class EglHelper { if (hasEglSurface()) { eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(mEglDisplay, mEglSurface); - mEglSurface = null; + mEglSurface = EGL_NO_SURFACE; } } @@ -296,7 +300,7 @@ public class EglHelper { public void destroyEglContext() { if (hasEglContext()) { eglDestroyContext(mEglDisplay, mEglContext); - mEglContext = null; + mEglContext = EGL_NO_CONTEXT; } } @@ -340,11 +344,16 @@ public class EglHelper { destroyEglContext(); } if (hasEglDisplay()) { - eglTerminate(mEglDisplay); + terminateEglDisplay(); } mEglReady = false; } + void terminateEglDisplay() { + eglTerminate(mEglDisplay); + mEglDisplay = EGL_NO_DISPLAY; + } + /** * Called to dump current state. * @param prefix prefix. diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index f9b18cf17abe..c7bfc06829b3 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -280,7 +280,9 @@ public class PipMenuActivityController { if (mToActivityMessenger != null) { Bundle data = new Bundle(); data.putInt(EXTRA_MENU_STATE, menuState); - data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); + if (stackBounds != null) { + data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); + } data.putParcelable(EXTRA_MOVEMENT_BOUNDS, movementBounds); data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout); data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index e1b61c670a12..e559694df375 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -109,10 +109,31 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); + /** + * Provides a new {@link TState} of the appropriate type to use between this tile and the + * corresponding view. + * + * @return new state to use by the tile. + */ public abstract TState newTileState(); + /** + * Handles clicks by the user. + * + * Calls to the controller should be made here to set the new state of the device. + */ abstract protected void handleClick(); + /** + * Update state of the tile based on device state + * + * Called whenever the state of the tile needs to be updated, either after user + * interaction or from callbacks from the controller. It populates {@code state} with the + * information to display to the user. + * + * @param state {@link TState} to populate with information to display + * @param arg additional arguments needed to populate {@code state} + */ abstract protected void handleUpdateState(TState state, Object arg); /** @@ -177,6 +198,12 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy return mHost; } + /** + * Return the {@link QSIconView} to be used by this tile's view. + * + * @param context view context for the view + * @return icon view for this tile + */ public QSIconView createTileView(Context context) { return new QSIconViewImpl(context); } @@ -298,11 +325,20 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mCallbacks.clear(); } + /** + * Handles secondary click on the tile. + * + * Defaults to {@link QSTileImpl#handleClick} + */ protected void handleSecondaryClick() { // Default to normal click. handleClick(); } + /** + * Handles long click on the tile by launching the {@link Intent} defined in + * {@link QSTileImpl#getLongClickIntent} + */ protected void handleLongClick() { if (mQSSettingsPanelOption == QSSettingsPanel.USE_DETAIL) { showDetail(true); @@ -312,6 +348,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy getLongClickIntent(), 0); } + /** + * Returns an intent to be launched when the tile is long pressed. + * + * @return the intent to launch + */ public abstract Intent getLongClickIntent(); protected void handleRefreshState(Object arg) { @@ -427,6 +468,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } } + /** + * Provides a default label for the tile. + * @return default label for the tile. + */ public abstract CharSequence getTileLabel(); public static int getColorForState(Context context, int state) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 001e09406e3a..42f80109e045 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Intent; import android.os.UserManager; import android.service.quicksettings.Tile; +import android.util.Log; import android.widget.Switch; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -200,6 +201,14 @@ public class HotspotTile extends QSTileImpl<BooleanState> { mCallbackInfo.numConnectedDevices = numDevices; refreshState(mCallbackInfo); } + + @Override + public void onHotspotAvailabilityChanged(boolean available) { + if (!available) { + Log.d(TAG, "Tile removed. Hotspot no longer available"); + mHost.removeTile(getTileSpec()); + } + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 880b8f8776e8..4f38a15824a4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -69,6 +69,7 @@ import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.animation.Interpolator; +import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; @@ -143,7 +144,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private static final float BACKGROUND_ALPHA = 0.5f; private static final float SCREENSHOT_DROP_IN_MIN_SCALE = 0.725f; private static final float ROUNDED_CORNER_RADIUS = .05f; - private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000; + private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000; private static final int MESSAGE_CORNER_TIMEOUT = 2; private final ScreenshotNotificationsController mNotificationsController; @@ -162,6 +163,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private final HorizontalScrollView mActionsContainer; private final LinearLayout mActionsView; private final ImageView mBackgroundProtection; + private final FrameLayout mDismissButton; private Bitmap mScreenBitmap; private AnimatorSet mScreenshotAnimation; @@ -170,6 +172,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private float mScreenshotOffsetYPx; private float mScreenshotHeightPx; private float mCornerScale; + private float mDismissButtonSize; private AsyncTask<Void, Void, Void> mSaveInBgTask; @@ -216,19 +219,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions); mBackgroundProtection = mScreenshotLayout.findViewById( R.id.global_screenshot_actions_background); + mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button); + mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button")); mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash); mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector); mScreenshotLayout.setFocusable(true); mScreenshotSelectorView.setFocusable(true); mScreenshotSelectorView.setFocusableInTouchMode(true); - mScreenshotLayout.setOnTouchListener((v, event) -> { - if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { - clearScreenshot("tap_outside"); - } - // Intercept and ignore all touch events - return true; - }); // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams( @@ -254,6 +252,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y); mCornerScale = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale) / (float) mDisplayMetrics.widthPixels; + mDismissButtonSize = resources.getDimensionPixelSize( + R.dimen.screenshot_dismiss_button_tappable_size); // Setup the Camera shutter sound mCameraSound = new MediaActionSound(); @@ -271,6 +271,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset Rect actionsRect = new Rect(); mActionsContainer.getBoundsOnScreen(actionsRect); touchRegion.op(actionsRect, Region.Op.UNION); + Rect dismissRect = new Rect(); + mDismissButton.getBoundsOnScreen(dismissRect); + touchRegion.op(dismissRect, Region.Op.UNION); inoutInfo.touchableRegion.set(touchRegion); } @@ -408,6 +411,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mActionsContainer.setVisibility(View.GONE); mBackgroundView.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); + mDismissButton.setVisibility(View.GONE); mScreenshotView.setVisibility(View.GONE); mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); } @@ -615,6 +619,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotView.setTranslationX(t * finalPos.x); mScreenshotView.setTranslationY(t * finalPos.y); }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + Rect bounds = new Rect(); + mScreenshotView.getBoundsOnScreen(bounds); + mDismissButton.setX(bounds.right - mDismissButtonSize / 2f); + mDismissButton.setY(bounds.top - mDismissButtonSize / 2f); + mDismissButton.setVisibility(View.VISIBLE); + } + }); return anim; } @@ -686,14 +701,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mBackgroundProtection.setAlpha(t); mActionsContainer.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t); }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - mScreenshotView.requestFocus(); - mScreenshotView.setElevation(50); - } - }); return animator; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 41c1b7b5fae8..006d40ddbac5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -69,6 +69,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; @@ -153,6 +154,7 @@ public final class NotificationEntry extends ListEntry { private NotificationEntry parent; // our parent (if we're in a group) private ExpandableNotificationRow row; // the outer expanded view + private ExpandableNotificationRowController mRowController; private int mCachedContrastColor = COLOR_INVALID; private int mCachedContrastColorIsFor = COLOR_INVALID; @@ -424,6 +426,14 @@ public final class NotificationEntry extends ListEntry { this.row = row; } + public ExpandableNotificationRowController getRowController() { + return mRowController; + } + + public void setRowController(ExpandableNotificationRowController controller) { + mRowController = controller; + } + @Nullable public List<NotificationEntry> getChildren() { if (row == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index ecf62db4680b..e8a62e48e75e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.collection.inflation; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; -import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP; import android.annotation.Nullable; @@ -40,19 +39,19 @@ import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotifBindPipeline; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; import com.android.systemui.statusbar.notification.row.RowContentBindParams; import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.notification.row.RowInflaterTask; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.Objects; @@ -67,35 +66,28 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private static final String TAG = "NotificationViewManager"; - private final NotificationGroupManager mGroupManager; - private final NotificationGutsManager mGutsManager; private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private final Context mContext; private final NotifBindPipeline mNotifBindPipeline; private final RowContentBindStage mRowContentBindStage; private final NotificationMessagingUtil mMessagingUtil; - private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = - this::logNotificationExpansion; private final NotificationRemoteInputManager mNotificationRemoteInputManager; private final NotificationLockscreenUserManager mNotificationLockscreenUserManager; - private final boolean mAllowLongPress; - private final KeyguardBypassController mKeyguardBypassController; - private final StatusBarStateController mStatusBarStateController; private NotificationPresenter mPresenter; private NotificationListContainer mListContainer; - private HeadsUpManager mHeadsUpManager; private NotificationRowContentBinder.InflationCallback mInflationCallback; - private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; private BindRowCallback mBindRowCallback; private NotificationClicker mNotificationClicker; private final Provider<RowInflaterTask> mRowInflaterTaskProvider; - private final NotificationLogger mNotificationLogger; + private final ExpandableNotificationRowComponent.Builder + mExpandableNotificationRowComponentBuilder; @Inject public NotificationRowBinderImpl( Context context, + NotificationMessagingUtil notificationMessagingUtil, NotificationRemoteInputManager notificationRemoteInputManager, NotificationLockscreenUserManager notificationLockscreenUserManager, NotifBindPipeline notifBindPipeline, @@ -107,21 +99,16 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { NotificationGutsManager notificationGutsManager, NotificationInterruptionStateProvider notificationInterruptionStateProvider, Provider<RowInflaterTask> rowInflaterTaskProvider, - NotificationLogger logger) { + ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder) { mContext = context; mNotifBindPipeline = notifBindPipeline; mRowContentBindStage = rowContentBindStage; - mMessagingUtil = new NotificationMessagingUtil(context); + mMessagingUtil = notificationMessagingUtil; mNotificationRemoteInputManager = notificationRemoteInputManager; mNotificationLockscreenUserManager = notificationLockscreenUserManager; - mAllowLongPress = allowLongPress; - mKeyguardBypassController = keyguardBypassController; - mStatusBarStateController = statusBarStateController; - mGroupManager = notificationGroupManager; - mGutsManager = notificationGutsManager; mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; mRowInflaterTaskProvider = rowInflaterTaskProvider; - mNotificationLogger = logger; + mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder; } /** @@ -129,13 +116,10 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { */ public void setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, - HeadsUpManager headsUpManager, BindRowCallback bindRowCallback) { mPresenter = presenter; mListContainer = listContainer; - mHeadsUpManager = headsUpManager; mBindRowCallback = bindRowCallback; - mOnAppOpsClickListener = mGutsManager::openGuts; } public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) { @@ -150,9 +134,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { * Inflates the views for the given entry (possibly asynchronously). */ @Override - public void inflateViews( - NotificationEntry entry, - Runnable onDismissRunnable) + public void inflateViews(NotificationEntry entry, Runnable onDismissRunnable) throws InflationException { ViewGroup parent = mListContainer.getViewParentForNotification(entry); PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext, @@ -163,12 +145,26 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { entry.updateIcons(mContext, sbn); entry.reset(); updateNotification(entry, pmUser, sbn, entry.getRow()); - entry.getRow().setOnDismissRunnable(onDismissRunnable); + entry.getRowController().setOnDismissRunnable(onDismissRunnable); } else { entry.createIcons(mContext, sbn); mRowInflaterTaskProvider.get().inflate(mContext, parent, entry, row -> { - bindRow(entry, pmUser, sbn, row, onDismissRunnable); + // Setup the controller for the view. + ExpandableNotificationRowComponent component = + mExpandableNotificationRowComponentBuilder + .expandableNotificationRow(row) + .notificationEntry(entry) + .onDismissRunnable(onDismissRunnable) + .inflationCallback(mInflationCallback) + .rowContentBindStage(mRowContentBindStage) + .onExpandClickListener(mPresenter) + .build(); + ExpandableNotificationRowController rowController = + component.getExpandableNotificationRowController(); + rowController.init(); + entry.setRowController(rowController); + bindRow(entry, pmUser, sbn, row); updateNotification(entry, pmUser, sbn, row); }); } @@ -176,55 +172,12 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { //TODO: This method associates a row with an entry, but eventually needs to not do that private void bindRow(NotificationEntry entry, PackageManager pmUser, - StatusBarNotification sbn, ExpandableNotificationRow row, - Runnable onDismissRunnable) { - // Get the app name. - // Note that Notification.Builder#bindHeaderAppName has similar logic - // but since this field is used in the guts, it must be accurate. - // Therefore we will only show the application label, or, failing that, the - // package name. No substitutions. - final String pkg = sbn.getPackageName(); - String appname = pkg; - try { - final ApplicationInfo info = pmUser.getApplicationInfo(pkg, - PackageManager.MATCH_UNINSTALLED_PACKAGES - | PackageManager.MATCH_DISABLED_COMPONENTS); - if (info != null) { - appname = String.valueOf(pmUser.getApplicationLabel(info)); - } - } catch (PackageManager.NameNotFoundException e) { - // Do nothing - } - - row.initialize( - appname, - sbn.getKey(), - mExpansionLogger, - mKeyguardBypassController, - mGroupManager, - mHeadsUpManager, - mRowContentBindStage, - mPresenter); - - // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely - row.setStatusBarStateController(mStatusBarStateController); - row.setAppOpsOnClickListener(mOnAppOpsClickListener); - if (mAllowLongPress) { - row.setLongPressListener(mGutsManager::openGuts); - } + StatusBarNotification sbn, ExpandableNotificationRow row) { mListContainer.bindRow(row); mNotificationRemoteInputManager.bindRow(row); - - row.setOnDismissRunnable(onDismissRunnable); - row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); - if (ENABLE_REMOTE_INPUT) { - row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); - } - entry.setRow(row); row.setEntry(entry); mNotifBindPipeline.manageRow(entry, row); - mBindRowCallback.onBindRow(entry, pmUser, sbn, row); } @@ -307,10 +260,6 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { Objects.requireNonNull(mNotificationClicker).register(row, sbn); } - private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { - mNotificationLogger.onExpansionChanged(key, userAction, expanded); - } - /** Callback for when a row is bound to an entry. */ public interface BindRowCallback { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 254b64ffcd90..3e0bcbb796bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -90,7 +90,6 @@ class NotificationsControllerImpl @Inject constructor( notificationRowBinder.setUpWithPresenter( presenter, listContainer, - headsUpManager, bindRowCallback) if (featureFlags.isNewNotifPipelineEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 50a20374fee5..3eac229af3f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -33,16 +33,14 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; -import com.android.systemui.Dependency; +import com.android.systemui.Gefingerpoken; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; -import com.android.systemui.statusbar.phone.DoubleTapHelper; /** * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf} @@ -94,14 +92,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR = new PathInterpolator(0, 0, 0.5f, 1); private int mTintedRippleColor; - protected int mNormalRippleColor; - private final AccessibilityManager mAccessibilityManager; - private final DoubleTapHelper mDoubleTapHelper; + private int mNormalRippleColor; + private Gefingerpoken mTouchHandler; private boolean mDimmed; - protected int mBgTint = NO_COLOR; - private float mBgAlpha = 1f; + int mBgTint = NO_COLOR; /** * Flag to indicate that the notification has been touched once and the second touch will @@ -116,7 +112,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private Interpolator mCurrentAppearInterpolator; private Interpolator mCurrentAlphaInterpolator; - protected NotificationBackgroundView mBackgroundNormal; + NotificationBackgroundView mBackgroundNormal; private NotificationBackgroundView mBackgroundDimmed; private ObjectAnimator mBackgroundAnimator; private RectF mAppearAnimationRect = new RectF(); @@ -130,7 +126,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private boolean mLastInSection; private boolean mFirstInSection; private boolean mIsBelowSpeedBump; - private final FalsingManager mFalsingManager; private float mNormalBackgroundVisibilityAmount; private float mDimmedBackgroundFadeInAmount = -1; @@ -154,38 +149,25 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView */ private boolean mNeedsDimming; private int mDimmedAlpha; - private boolean mBlockNextTouch; private boolean mIsHeadsUpAnimation; private int mHeadsUpAddStartLocation; private float mHeadsUpLocation; private boolean mIsAppearing; private boolean mDismissed; private boolean mRefocusOnDismiss; + private OnDimmedListener mOnDimmedListener; + private AccessibilityManager mAccessibilityManager; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f); mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f); - mFalsingManager = Dependency.get(FalsingManager.class); // TODO: inject into a controller. setClipChildren(false); setClipToPadding(false); updateColors(); - mAccessibilityManager = AccessibilityManager.getInstance(mContext); - - mDoubleTapHelper = new DoubleTapHelper(this, (active) -> { - if (active) { - makeActive(); - } else { - makeInactive(true /* animate */); - } - }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); initDimens(); } - public FalsingManager getFalsingManager() { - return mFalsingManager; - } - private void updateColors() { mNormalColor = mContext.getColor(R.color.notification_material_background_color); mTintedRippleColor = mContext.getColor( @@ -236,32 +218,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim); } - private final Runnable mTapTimeoutRunnable = new Runnable() { - @Override - public void run() { - makeInactive(true /* animate */); - } - }; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN - && disallowSingleClick(ev) && !isTouchExplorationEnabled()) { - if (!mActivated) { - return true; - } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { - mBlockNextTouch = true; - makeInactive(true /* animate */); - return true; - } + if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) { + return true; } return super.onInterceptTouchEvent(ev); } - private boolean isTouchExplorationEnabled() { - return mAccessibilityManager.isTouchExplorationEnabled(); - } - protected boolean disallowSingleClick(MotionEvent ev) { return false; } @@ -270,25 +235,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return false; } - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result; - if (mBlockNextTouch) { - mBlockNextTouch = false; - return false; - } - if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) { - boolean wasActivated = mActivated; - result = handleTouchEventDimmed(event); - if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) { - removeCallbacks(mTapTimeoutRunnable); - } - } else { - result = super.onTouchEvent(event); - } - return result; - } - /** * @return whether this view is interactive and can be double tapped */ @@ -313,28 +259,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - public void setRippleAllowed(boolean allowed) { + void setRippleAllowed(boolean allowed) { mBackgroundNormal.setPressedAllowed(allowed); } - private boolean handleTouchEventDimmed(MotionEvent event) { - if (mNeedsDimming && !mDimmed) { - // We're actually dimmed, but our content isn't dimmable, let's ensure we have a ripple - super.onTouchEvent(event); - } - return mDoubleTapHelper.onTouchEvent(event, getActualHeight()); - } - - @Override - public boolean performClick() { - if (!mNeedsDimming || isTouchExplorationEnabled()) { - return super.performClick(); - } - return false; - } - - private void makeActive() { - mFalsingManager.onNotificationActive(); + void makeActive() { startActivateAnimation(false /* reverse */); mActivated = true; if (mOnActivatedListener != null) { @@ -388,19 +317,25 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundNormal.animate() .alpha(reverse ? 0f : 1f) .setInterpolator(alphaInterpolator) - .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float animatedFraction = animation.getAnimatedFraction(); - if (reverse) { - animatedFraction = 1.0f - animatedFraction; - } - setNormalBackgroundVisibilityAmount(animatedFraction); + .setUpdateListener(animation -> { + float animatedFraction = animation.getAnimatedFraction(); + if (reverse) { + animatedFraction = 1.0f - animatedFraction; } + setNormalBackgroundVisibilityAmount(animatedFraction); }) .setDuration(ACTIVATE_ANIMATION_LENGTH); } + @Override + public boolean performClick() { + if (!mNeedsDimming || (mAccessibilityManager != null + && mAccessibilityManager.isTouchExplorationEnabled())) { + return super.performClick(); + } + return false; + } + /** * Cancels the hotspot and makes the notification inactive. */ @@ -418,11 +353,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (mOnActivatedListener != null) { mOnActivatedListener.onActivationReset(this); } - removeCallbacks(mTapTimeoutRunnable); } public void setDimmed(boolean dimmed, boolean fade) { mNeedsDimming = dimmed; + if (mOnDimmedListener != null) { + mOnDimmedListener.onSetDimmed(dimmed); + } dimmed &= isDimmable(); if (mDimmed != dimmed) { mDimmed = dimmed; @@ -439,13 +376,17 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return true; } + public boolean isDimmed() { + return mDimmed; + } + private void updateOutlineAlpha() { float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED; alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount); setOutlineAlpha(alpha); } - public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) { + private void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) { mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount; updateOutlineAlpha(); } @@ -473,14 +414,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView /** * Sets the tint color of the background */ - public void setTintColor(int color) { + protected void setTintColor(int color) { setTintColor(color, false); } /** * Sets the tint color of the background */ - public void setTintColor(int color, boolean animated) { + void setTintColor(int color, boolean animated) { if (color != mBgTint) { mBgTint = color; updateBackgroundTint(animated); @@ -562,13 +503,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mStartTint = mCurrentBackgroundTint; mTargetTint = color; mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); - mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, - animation.getAnimatedFraction()); - setBackgroundTintColor(newColor); - } + mBackgroundColorAnimator.addUpdateListener(animation -> { + int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, + animation.getAnimatedFraction()); + setBackgroundTintColor(newColor); }); mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); @@ -643,11 +581,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } protected void updateBackgroundAlpha(float transformationAmount) { - mBgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f; + float bgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f; if (mDimmedBackgroundFadeInAmount != -1) { - mBgAlpha *= mDimmedBackgroundFadeInAmount; + bgAlpha *= mDimmedBackgroundFadeInAmount; } - mBackgroundDimmed.setAlpha(mBgAlpha); + mBackgroundDimmed.setAlpha(bgAlpha); } protected void resetBackgroundAlpha() { @@ -671,7 +609,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mBackgroundDimmed.setVisibility(View.INVISIBLE); mBackgroundNormal.setVisibility(View.VISIBLE); mBackgroundNormal.setAlpha(1f); - removeCallbacks(mTapTimeoutRunnable); // make in inactive to avoid it sticking around active makeInactive(false /* animate */); } @@ -783,14 +720,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAppearAnimator.setInterpolator(Interpolators.LINEAR); mAppearAnimator.setDuration( (long) (duration * Math.abs(mAppearAnimationFraction - targetValue))); - mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mAppearAnimationFraction = (float) animation.getAnimatedValue(); - updateAppearAnimationAlpha(); - updateAppearRect(); - invalidate(); - } + mAppearAnimator.addUpdateListener(animation -> { + mAppearAnimationFraction = (float) animation.getAnimatedValue(); + updateAppearAnimationAlpha(); + updateAppearRect(); + invalidate(); }); if (animationListener != null) { mAppearAnimator.addListener(animationListener); @@ -921,7 +855,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView getCurrentBackgroundRadiusBottom()); } - protected void applyBackgroundRoundness(float topRadius, float bottomRadius) { + private void applyBackgroundRoundness(float topRadius, float bottomRadius) { mBackgroundDimmed.setRoundness(topRadius, bottomRadius); mBackgroundNormal.setRoundness(topRadius, bottomRadius); } @@ -963,7 +897,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - protected int getRippleColor() { + private int getRippleColor() { if (mBgTint != 0) { return mTintedRippleColor; } else { @@ -1010,10 +944,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mOnActivatedListener = onActivatedListener; } - public boolean hasSameBgColor(ActivatableNotificationView otherView) { - return calculateBgColor() == otherView.calculateBgColor(); - } - @Override public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, int outlineTranslation) { @@ -1071,8 +1001,24 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return mRefocusOnDismiss || isAccessibilityFocused(); } + void setTouchHandler(Gefingerpoken touchHandler) { + mTouchHandler = touchHandler; + } + + void setOnDimmedListener(OnDimmedListener onDimmedListener) { + mOnDimmedListener = onDimmedListener; + } + + public void setAccessibilityManager(AccessibilityManager accessibilityManager) { + mAccessibilityManager = accessibilityManager; + } + public interface OnActivatedListener { void onActivated(ActivatableNotificationView view); void onActivationReset(ActivatableNotificationView view); } + + interface OnDimmedListener { + void onSetDimmed(boolean dimmed); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java index 18993ffec357..8465658079f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java @@ -16,9 +16,13 @@ package com.android.systemui.statusbar.notification.row; +import android.view.MotionEvent; +import android.view.View; import android.view.accessibility.AccessibilityManager; +import com.android.systemui.Gefingerpoken; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.statusbar.phone.DoubleTapHelper; import javax.inject.Inject; @@ -27,21 +31,102 @@ import javax.inject.Inject; */ public class ActivatableNotificationViewController { private final ActivatableNotificationView mView; + private final ExpandableOutlineViewController mExpandableOutlineViewController; private final AccessibilityManager mAccessibilityManager; private final FalsingManager mFalsingManager; + private DoubleTapHelper mDoubleTapHelper; + private boolean mNeedsDimming; + + private TouchHandler mTouchHandler = new TouchHandler(); @Inject public ActivatableNotificationViewController(ActivatableNotificationView view, + ExpandableOutlineViewController expandableOutlineViewController, AccessibilityManager accessibilityManager, FalsingManager falsingManager) { mView = view; + mExpandableOutlineViewController = expandableOutlineViewController; mAccessibilityManager = accessibilityManager; mFalsingManager = falsingManager; + + mView.setOnActivatedListener(new ActivatableNotificationView.OnActivatedListener() { + @Override + public void onActivated(ActivatableNotificationView view) { + mFalsingManager.onNotificationActive(); + } + + @Override + public void onActivationReset(ActivatableNotificationView view) { + } + }); } /** * Initialize the controller, setting up handlers and other behavior. */ public void init() { + mExpandableOutlineViewController.init(); + mDoubleTapHelper = new DoubleTapHelper(mView, (active) -> { + if (active) { + mView.makeActive(); + mFalsingManager.onNotificationActive(); + } else { + mView.makeInactive(true /* animate */); + } + }, mView::performClick, mView::handleSlideBack, mFalsingManager::onNotificationDoubleTap); + mView.setOnTouchListener(mTouchHandler); + mView.setTouchHandler(mTouchHandler); + mView.setOnDimmedListener(dimmed -> { + mNeedsDimming = dimmed; + }); + mView.setAccessibilityManager(mAccessibilityManager); + } + + class TouchHandler implements Gefingerpoken, View.OnTouchListener { + private boolean mBlockNextTouch; + + @Override + public boolean onTouch(View v, MotionEvent ev) { + boolean result; + if (mBlockNextTouch) { + mBlockNextTouch = false; + return true; + } + if (mNeedsDimming && !mAccessibilityManager.isTouchExplorationEnabled() + && mView.isInteractive()) { + if (mNeedsDimming && !mView.isDimmed()) { + // We're actually dimmed, but our content isn't dimmable, + // let's ensure we have a ripple + return false; + } + result = mDoubleTapHelper.onTouchEvent(ev, mView.getActualHeight()); + } else { + return false; + } + return result; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN + && mView.disallowSingleClick(ev) + && !mAccessibilityManager.isTouchExplorationEnabled()) { + if (!mView.isActivated()) { + return true; + } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) { + mBlockNextTouch = true; + mView.makeInactive(true /* animate */); + return true; + } + } + return false; + } + /** + * Use {@link #onTouch(View, MotionEvent) instead}. + */ + @Override + public boolean onTouchEvent(MotionEvent ev) { + return false; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index c34bba782ad5..551731824570 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -42,7 +42,6 @@ import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.SystemClock; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.AttributeSet; @@ -72,11 +71,11 @@ import com.android.internal.widget.CachingIconView; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarIconView; @@ -199,6 +198,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationGuts mGuts; private NotificationEntry mEntry; private String mAppName; + private FalsingManager mFalsingManager; /** * Whether or not the notification is using the heads up view and should peek from the top. @@ -1087,20 +1087,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mEntry.setInitializationTime(SystemClock.elapsedRealtime()); - Dependency.get(PluginManager.class).addPluginListener(this, - NotificationMenuRowPlugin.class, false /* Allow multiple */); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Dependency.get(PluginManager.class).removePluginListener(this); - } - - @Override public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) { boolean existed = mMenuRow != null && mMenuRow.getMenuView() != null; if (existed) { @@ -1439,7 +1425,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mIsBlockingHelperShowing && mNotificationTranslationFinished; } - public void setOnDismissRunnable(Runnable onDismissRunnable) { + void setOnDismissRunnable(Runnable onDismissRunnable) { mOnDismissRunnable = onDismissRunnable; } @@ -1597,7 +1583,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mMenuRow = new NotificationMenuRow(mContext); mImageResolver = new NotificationInlineImageResolver(context, new NotificationInlineImageCache()); - mMediaManager = Dependency.get(NotificationMediaManager.class); initDimens(); } @@ -1612,7 +1597,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView NotificationGroupManager groupManager, HeadsUpManager headsUpManager, RowContentBindStage rowContentBindStage, - OnExpandClickListener onExpandClickListener) { + OnExpandClickListener onExpandClickListener, + NotificationMediaManager notificationMediaManager, + OnAppOpsClickListener onAppOpsClickListener, + FalsingManager falsingManager, + StatusBarStateController statusBarStateController) { mAppName = appName; if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.setAppName(mAppName); @@ -1625,9 +1614,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mHeadsUpManager = headsUpManager; mRowContentBindStage = rowContentBindStage; mOnExpandClickListener = onExpandClickListener; - } - - public void setStatusBarStateController(StatusBarStateController statusBarStateController) { + mMediaManager = notificationMediaManager; + setAppOpsOnClickListener(onAppOpsClickListener); + mFalsingManager = falsingManager; mStatusbarStateController = statusBarStateController; } @@ -1719,7 +1708,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mOnAppOpsClickListener; } - public void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) { + void setAppOpsOnClickListener(ExpandableNotificationRow.OnAppOpsClickListener l) { mOnAppOpsClickListener = v -> { createMenu(); NotificationMenuRowPlugin provider = getProvider(); @@ -2188,7 +2177,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * @param allowChildExpansion whether a call to this method allows expanding children */ public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { - getFalsingManager().setNotificationExpanded(); + mFalsingManager.setNotificationExpanded(); if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion && !mChildrenContainer.showingAsLowPriority()) { final boolean wasExpanded = mGroupManager.isGroupExpanded(mEntry.getSbn()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java new file mode 100644 index 000000000000..39fab439ad07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; +import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; + +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.row.dagger.AppName; +import com.android.systemui.statusbar.notification.row.dagger.DismissRunnable; +import com.android.systemui.statusbar.notification.row.dagger.NotificationKey; +import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.util.time.SystemClock; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Controller for {@link ExpandableNotificationRow}. + */ +@NotificationRowScope +public class ExpandableNotificationRowController { + private final ExpandableNotificationRow mView; + private final ActivatableNotificationViewController mActivatableNotificationViewController; + private final NotificationMediaManager mMediaManager; + private final PluginManager mPluginManager; + private final SystemClock mClock; + private final String mAppName; + private final String mNotificationKey; + private final KeyguardBypassController mKeyguardBypassController; + private final NotificationGroupManager mNotificationGroupManager; + private final RowContentBindStage mRowContentBindStage; + private final NotificationLogger mNotificationLogger; + private final HeadsUpManager mHeadsUpManager; + private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener; + private final StatusBarStateController mStatusBarStateController; + private final NotificationRowContentBinder.InflationCallback mInflationCallback; + + private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger = + this::logNotificationExpansion; + private final ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener; + private final NotificationGutsManager mNotificationGutsManager; + private Runnable mOnDismissRunnable; + private final FalsingManager mFalsingManager; + private final boolean mAllowLongPress; + + @Inject + public ExpandableNotificationRowController(ExpandableNotificationRow view, + ActivatableNotificationViewController activatableNotificationViewController, + NotificationMediaManager mediaManager, PluginManager pluginManager, + SystemClock clock, @AppName String appName, @NotificationKey String notificationKey, + KeyguardBypassController keyguardBypassController, + NotificationGroupManager notificationGroupManager, + RowContentBindStage rowContentBindStage, + NotificationLogger notificationLogger, HeadsUpManager headsUpManager, + ExpandableNotificationRow.OnExpandClickListener onExpandClickListener, + StatusBarStateController statusBarStateController, + NotificationRowContentBinder.InflationCallback inflationCallback, + NotificationGutsManager notificationGutsManager, + @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, + @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager) { + mView = view; + mActivatableNotificationViewController = activatableNotificationViewController; + mMediaManager = mediaManager; + mPluginManager = pluginManager; + mClock = clock; + mAppName = appName; + mNotificationKey = notificationKey; + mKeyguardBypassController = keyguardBypassController; + mNotificationGroupManager = notificationGroupManager; + mRowContentBindStage = rowContentBindStage; + mNotificationLogger = notificationLogger; + mHeadsUpManager = headsUpManager; + mOnExpandClickListener = onExpandClickListener; + mStatusBarStateController = statusBarStateController; + mInflationCallback = inflationCallback; + mNotificationGutsManager = notificationGutsManager; + mOnDismissRunnable = onDismissRunnable; + mOnAppOpsClickListener = mNotificationGutsManager::openGuts; + mAllowLongPress = allowLongPress; + mFalsingManager = falsingManager; + } + + /** + * Initialize the controller. + */ + public void init() { + mActivatableNotificationViewController.init(); + mView.initialize( + mAppName, + mNotificationKey, + mExpansionLogger, + mKeyguardBypassController, + mNotificationGroupManager, + mHeadsUpManager, + mRowContentBindStage, + mOnExpandClickListener, + mMediaManager, + mOnAppOpsClickListener, + mFalsingManager, + mStatusBarStateController + ); + mView.setOnDismissRunnable(mOnDismissRunnable); + mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + if (mAllowLongPress) { + mView.setLongPressListener(mNotificationGutsManager::openGuts); + } + if (ENABLE_REMOTE_INPUT) { + mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); + } + + mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + mView.getEntry().setInitializationTime(mClock.elapsedRealtime()); + mPluginManager.addPluginListener(mView, + NotificationMenuRowPlugin.class, false /* Allow multiple */); + } + + @Override + public void onViewDetachedFromWindow(View v) { + mPluginManager.removePluginListener(mView); + } + }); + } + + private void logNotificationExpansion(String key, boolean userAction, boolean expanded) { + mNotificationLogger.onExpansionChanged(key, userAction, expanded); + } + + /** */ + public void setOnDismissRunnable(Runnable onDismissRunnable) { + mOnDismissRunnable = onDismissRunnable; + mView.setOnDismissRunnable(onDismissRunnable); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java new file mode 100644 index 000000000000..75c9d1e6f2fc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineViewController.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import javax.inject.Inject; + +/** + * Controller for {@link ExpandableOutlineView}. + */ +public class ExpandableOutlineViewController { + private final ExpandableOutlineView mView; + private final ExpandableViewController mExpandableViewController; + + @Inject + public ExpandableOutlineViewController(ExpandableOutlineView view, + ExpandableViewController expandableViewController) { + mView = view; + mExpandableViewController = expandableViewController; + } + + /** + * Initialize the controller. + */ + public void init() { + mExpandableViewController.init(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java new file mode 100644 index 000000000000..e14ca8c4e590 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableViewController.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row; + +import javax.inject.Inject; + +/** + * Controller for {@link ExpandableView}. + */ +public class ExpandableViewController { + private final ExpandableView mView; + + @Inject + public ExpandableViewController(ExpandableView view) { + mView = view; + } + + /** + * Initialize the controller. + */ + public void init() { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index bb0681ce8a44..a0af4ace05b1 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 @@ -23,14 +23,6 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_DEMOTE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE; -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; import android.annotation.IntDef; @@ -69,11 +61,13 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.phone.ShadeController; import java.lang.annotation.Retention; import java.util.Arrays; @@ -92,6 +86,7 @@ public class NotificationConversationInfo extends LinearLayout implements ShortcutManager mShortcutManager; private PackageManager mPm; private VisualStabilityManager mVisualStabilityManager; + private ShadeController mShadeController; private String mPackageName; private String mAppName; @@ -103,24 +98,30 @@ public class NotificationConversationInfo extends LinearLayout implements private NotificationEntry mEntry; private StatusBarNotification mSbn; private boolean mIsDeviceProvisioned; - private int mStartingChannelImportance; private boolean mStartedAsBubble; private boolean mIsBubbleable; - // TODO: remove when launcher api works - @VisibleForTesting - boolean mShowHomeScreen = false; - private @UpdateChannelRunnable.Action int mSelectedAction = -1; + private @Action int mSelectedAction = -1; private OnSnoozeClickListener mOnSnoozeClickListener; private OnSettingsClickListener mOnSettingsClickListener; - private OnAppSettingsClickListener mAppSettingsClickListener; private NotificationGuts mGutsContainer; private BubbleController mBubbleController; @VisibleForTesting boolean mSkipPost = false; + @Retention(SOURCE) + @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE}) + private @interface Action {} + static final int ACTION_BUBBLE = 0; + static final int ACTION_HOME = 1; + static final int ACTION_FAVORITE = 2; + static final int ACTION_SNOOZE = 3; + static final int ACTION_MUTE = 4; + static final int ACTION_SETTINGS = 5; + static final int ACTION_UNBUBBLE = 6; + private OnClickListener mOnBubbleClick = v -> { mSelectedAction = mStartedAsBubble ? ACTION_UNBUBBLE : ACTION_BUBBLE; if (mStartedAsBubble) { @@ -136,12 +137,14 @@ public class NotificationConversationInfo extends LinearLayout implements private OnClickListener mOnHomeClick = v -> { mSelectedAction = ACTION_HOME; mShortcutManager.requestPinShortcut(mShortcutInfo, null); + mShadeController.animateCollapsePanels(); closeControls(v, true); }; private OnClickListener mOnFavoriteClick = v -> { mSelectedAction = ACTION_FAVORITE; - closeControls(v, true); + updateChannel(); + }; private OnClickListener mOnSnoozeClick = v -> { @@ -151,13 +154,8 @@ public class NotificationConversationInfo extends LinearLayout implements }; private OnClickListener mOnMuteClick = v -> { - mSelectedAction = ACTION_MUTE; - closeControls(v, true); - }; - - private OnClickListener mOnDemoteClick = v -> { - mSelectedAction = ACTION_DEMOTE; - closeControls(v, true); + mSelectedAction = ACTION_MUTE; + updateChannel(); }; public NotificationConversationInfo(Context context, AttributeSet attrs) { @@ -197,15 +195,14 @@ public class NotificationConversationInfo extends LinearLayout implements mEntry = entry; mSbn = entry.getSbn(); mPm = pm; - mAppSettingsClickListener = onAppSettingsClick; mAppName = mPackageName; mOnSettingsClickListener = onSettingsClick; mNotificationChannel = notificationChannel; - mStartingChannelImportance = mNotificationChannel.getImportance(); mAppUid = mSbn.getUid(); mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; mOnSnoozeClickListener = onSnoozeClickListener; + mShadeController = Dependency.get(ShadeController.class); mShortcutManager = shortcutManager; mLauncherApps = launcherApps; @@ -251,9 +248,6 @@ public class NotificationConversationInfo extends LinearLayout implements mNotificationChannel = mINotificationManager.getConversationNotificationChannel( mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName, mNotificationChannel.getId(), false, mConversationId); - - // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a - // time } catch (RemoteException e) { Slog.e(TAG, "Could not create conversation channel", e); } @@ -274,40 +268,24 @@ public class NotificationConversationInfo extends LinearLayout implements Button home = findViewById(R.id.home); home.setOnClickListener(mOnHomeClick); - home.setVisibility(mShowHomeScreen && mShortcutInfo != null + home.setVisibility(mShortcutInfo != null && mShortcutManager.isRequestPinShortcutSupported() ? VISIBLE : GONE); - Button favorite = findViewById(R.id.fave); + View favorite = findViewById(R.id.fave); favorite.setOnClickListener(mOnFavoriteClick); - if (mNotificationChannel.isImportantConversation()) { - favorite.setText(R.string.notification_conversation_unfavorite); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_star), null, null, null); - } else { - favorite.setText(R.string.notification_conversation_favorite); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_star_border), null, null, null); - } Button snooze = findViewById(R.id.snooze); snooze.setOnClickListener(mOnSnoozeClick); - Button mute = findViewById(R.id.mute); + View mute = findViewById(R.id.mute); mute.setOnClickListener(mOnMuteClick); - if (mStartingChannelImportance >= IMPORTANCE_DEFAULT - || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) { - mute.setText(R.string.notification_conversation_mute); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null); - } else { - mute.setText(R.string.notification_conversation_unmute); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null); - } - ImageButton demote = findViewById(R.id.demote); - demote.setOnClickListener(mOnDemoteClick); + final View settingsButton = findViewById(R.id.info); + settingsButton.setOnClickListener(getSettingsOnClickListener()); + settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); + + updateToggleActions(); } private void bindHeader() { @@ -315,26 +293,6 @@ public class NotificationConversationInfo extends LinearLayout implements // Delegate bindDelegate(); - - // Set up app settings link (i.e. Customize) - View settingsLinkView = findViewById(R.id.app_settings); - Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, - mNotificationChannel, - mSbn.getId(), mSbn.getTag()); - if (settingsIntent != null - && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { - settingsLinkView.setVisibility(VISIBLE); - settingsLinkView.setOnClickListener((View view) -> { - mAppSettingsClickListener.onClick(view, settingsIntent); - }); - } else { - settingsLinkView.setVisibility(View.GONE); - } - - // System Settings button. - final View settingsButton = findViewById(R.id.info); - settingsButton.setOnClickListener(getSettingsOnClickListener()); - settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); } private OnClickListener getSettingsOnClickListener() { @@ -424,15 +382,12 @@ public class NotificationConversationInfo extends LinearLayout implements private void bindDelegate() { TextView delegateView = findViewById(R.id.delegate_name); - TextView dividerView = findViewById(R.id.pkg_divider); if (!TextUtils.equals(mPackageName, mDelegatePkg)) { // this notification was posted by a delegate! delegateView.setVisibility(View.VISIBLE); - dividerView.setVisibility(View.VISIBLE); } else { delegateView.setVisibility(View.GONE); - dividerView.setVisibility(View.GONE); } } @@ -492,26 +447,37 @@ public class NotificationConversationInfo extends LinearLayout implements } } - private Intent getAppSettingsIntent(PackageManager pm, String packageName, - NotificationChannel channel, int id, String tag) { - Intent intent = new Intent(Intent.ACTION_MAIN) - .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) - .setPackage(packageName); - final List<ResolveInfo> resolveInfos = pm.queryIntentActivities( - intent, - PackageManager.MATCH_DEFAULT_ONLY - ); - if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) { - return null; + private void updateToggleActions() { + ImageButton favorite = findViewById(R.id.fave); + if (mNotificationChannel.isImportantConversation()) { + favorite.setContentDescription( + mContext.getString(R.string.notification_conversation_favorite)); + favorite.setImageResource(R.drawable.ic_important); + } else { + favorite.setContentDescription( + mContext.getString(R.string.notification_conversation_unfavorite)); + favorite.setImageResource(R.drawable.ic_important_outline); } - final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; - intent.setClassName(activityInfo.packageName, activityInfo.name); - if (channel != null) { - intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId()); + + ImageButton mute = findViewById(R.id.mute); + if (mNotificationChannel.getImportance() >= IMPORTANCE_DEFAULT + || mNotificationChannel.getImportance() == IMPORTANCE_UNSPECIFIED) { + mute.setContentDescription( + mContext.getString(R.string.notification_conversation_unmute)); + mute.setImageResource(R.drawable.ic_notifications_alert); + } else { + mute.setContentDescription( + mContext.getString(R.string.notification_conversation_mute)); + mute.setImageResource(R.drawable.ic_notifications_silence); } - intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id); - intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag); - return intent; + } + + private void updateChannel() { + Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); + bgHandler.post( + new UpdateChannelRunnable(mINotificationManager, mPackageName, + mAppUid, mSelectedAction, mNotificationChannel)); + mVisualStabilityManager.temporarilyAllowReordering(); } /** @@ -556,11 +522,7 @@ public class NotificationConversationInfo extends LinearLayout implements @Override public boolean handleCloseControls(boolean save, boolean force) { if (save && mSelectedAction > -1) { - Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); - bgHandler.post( - new UpdateChannelRunnable(mINotificationManager, mPackageName, - mAppUid, mSelectedAction, mNotificationChannel)); - mVisualStabilityManager.temporarilyAllowReordering(); + updateChannel(); } return false; } @@ -575,19 +537,7 @@ public class NotificationConversationInfo extends LinearLayout implements return false; } - static class UpdateChannelRunnable implements Runnable { - - @Retention(SOURCE) - @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE, - ACTION_DEMOTE}) - private @interface Action {} - static final int ACTION_BUBBLE = 0; - static final int ACTION_HOME = 1; - static final int ACTION_FAVORITE = 2; - static final int ACTION_SNOOZE = 3; - static final int ACTION_MUTE = 4; - static final int ACTION_DEMOTE = 5; - static final int ACTION_UNBUBBLE = 6; + class UpdateChannelRunnable implements Runnable { private final INotificationManager mINotificationManager; private final String mAppPkg; @@ -633,10 +583,6 @@ public class NotificationConversationInfo extends LinearLayout implements mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT)); } break; - case ACTION_DEMOTE: - mChannelToUpdate.setDemoted(!mChannelToUpdate.isDemoted()); - break; - } if (channelSettingChanged) { @@ -646,13 +592,7 @@ public class NotificationConversationInfo extends LinearLayout implements } catch (RemoteException e) { Log.e(TAG, "Unable to update notification channel", e); } + ThreadUtils.postOnMainThread(() -> updateToggleActions()); } } - - @Retention(SOURCE) - @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE}) - private @interface AlertingBehavior {} - private static final int BEHAVIOR_ALERTING = 0; - private static final int BEHAVIOR_SILENT = 1; - private static final int BEHAVIOR_BUBBLE = 2; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6789c814dcee..352abcfc9214 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -384,6 +385,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx guts.resetFalsingCheck(); mOnSettingsClickListener.onSettingsClick(sbn.getKey()); startAppNotificationSettingsActivity(packageName, appUid, channel, row); + notificationInfoView.closeControls(v, false); }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java index c173b4dbaebe..6feffe654630 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java @@ -26,7 +26,6 @@ import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import com.android.systemui.R; import com.android.systemui.statusbar.InflationTask; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import javax.inject.Inject; @@ -37,7 +36,6 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private static final String TAG = "RowInflaterTask"; private static final boolean TRACE_ORIGIN = true; - private final NotificationRowComponent.Builder mNotificationRowComponentBuilder; private RowInflationFinishedListener mListener; private NotificationEntry mEntry; @@ -45,10 +43,7 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private Throwable mInflateOrigin; @Inject - public RowInflaterTask( - NotificationRowComponent.Builder notificationRowComponentBuilder) { - super(); - mNotificationRowComponentBuilder = notificationRowComponentBuilder; + public RowInflaterTask() { } /** @@ -75,12 +70,6 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf public void onInflateFinished(View view, int resid, ViewGroup parent) { if (!mCancelled) { try { - // Setup the controller for the view. - NotificationRowComponent component = mNotificationRowComponentBuilder - .activatableNotificationView((ActivatableNotificationView) view) - .build(); - component.getActivatableNotificationViewController().init(); - mEntry.onInflationTaskFinished(); mListener.onInflationFinished((ExpandableNotificationRow) view); } catch (Throwable t) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java new file mode 100644 index 000000000000..a3dfa608c709 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ActivatableNotificationViewModule.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.ExpandableOutlineView; +import com.android.systemui.statusbar.notification.row.ExpandableView; + +import dagger.Binds; +import dagger.Module; + +/** + * Module for NotificationRowComponent. + */ +@Module +public interface ActivatableNotificationViewModule { + /** ExpandableView is provided as an instance of ActivatableNotificationView. */ + @Binds + ExpandableView bindExpandableView(ActivatableNotificationView view); + /** ExpandableOutlineView is provided as an instance of ActivatableNotificationView. */ + @Binds + ExpandableOutlineView bindExpandableOutlineView(ActivatableNotificationView view); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java new file mode 100644 index 000000000000..1dbca0cd5527 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/AppName.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface AppName { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java new file mode 100644 index 000000000000..433114224289 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/DismissRunnable.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface DismissRunnable { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java new file mode 100644 index 000000000000..6d6d3e446f53 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.service.notification.StatusBarNotification; + +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder; +import com.android.systemui.statusbar.notification.row.RowContentBindStage; +import com.android.systemui.statusbar.phone.StatusBar; + +import dagger.Binds; +import dagger.BindsInstance; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * Dagger Component for a {@link ExpandableNotificationRow}. + */ +@Subcomponent(modules = {ExpandableNotificationRowComponent.ExpandableNotificationRowModule.class, + ActivatableNotificationViewModule.class}) +@NotificationRowScope +public interface ExpandableNotificationRowComponent { + + /** + * Builder for {@link NotificationRowComponent}. + */ + @Subcomponent.Builder + interface Builder { + // TODO: NotificationEntry contains a reference to ExpandableNotificationRow, so it + // should be possible to pull one from the other, but they aren't connected at the time + // this component is constructed. + @BindsInstance + Builder expandableNotificationRow(ExpandableNotificationRow view); + @BindsInstance + Builder notificationEntry(NotificationEntry entry); + @BindsInstance + Builder onDismissRunnable(@DismissRunnable Runnable runnable); + @BindsInstance + Builder rowContentBindStage(RowContentBindStage rowContentBindStage); + @BindsInstance + Builder inflationCallback(NotificationRowContentBinder.InflationCallback inflationCallback); + @BindsInstance + Builder onExpandClickListener(ExpandableNotificationRow.OnExpandClickListener presenter); + ExpandableNotificationRowComponent build(); + } + + /** + * Creates a ExpandableNotificationRowController. + */ + @NotificationRowScope + ExpandableNotificationRowController getExpandableNotificationRowController(); + + /** + * Dagger Module that extracts interesting properties from an ExpandableNotificationRow. + */ + @Module + abstract class ExpandableNotificationRowModule { + + /** ExpandableNotificationRow is provided as an instance of ActivatableNotificationView. */ + @Binds + abstract ActivatableNotificationView bindExpandableView(ExpandableNotificationRow view); + + @Provides + static StatusBarNotification provideStatusBarNotification( + NotificationEntry notificationEntry) { + return notificationEntry.getSbn(); + } + + @Provides + @NotificationKey + static String provideNotificationKey(StatusBarNotification statusBarNotification) { + return statusBarNotification.getKey(); + } + + @Provides + @AppName + static String provideAppName(Context context, StatusBarNotification statusBarNotification) { + // Get the app name. + // Note that Notification.Builder#bindHeaderAppName has similar logic + // but since this field is used in the guts, it must be accurate. + // Therefore we will only show the application label, or, failing that, the + // package name. No substitutions. + PackageManager pmUser = StatusBar.getPackageManagerForUser( + context, statusBarNotification.getUser().getIdentifier()); + final String pkg = statusBarNotification.getPackageName(); + try { + final ApplicationInfo info = pmUser.getApplicationInfo(pkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS); + if (info != null) { + return String.valueOf(pmUser.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) { + // Do nothing + } + + return pkg; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java new file mode 100644 index 000000000000..b1fff383cd5d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationKey.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface NotificationKey { +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java index f16ea7ae23e9..1f535c5e3f56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowComponent.java @@ -16,24 +16,17 @@ package com.android.systemui.statusbar.notification.row.dagger; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import javax.inject.Scope; - import dagger.BindsInstance; import dagger.Subcomponent; /** * Dagger subcomponent for Notification related views. */ -@Subcomponent(modules = {}) -@NotificationRowComponent.NotificationRowScope +@Subcomponent(modules = {ActivatableNotificationViewModule.class}) +@NotificationRowScope public interface NotificationRowComponent { /** * Builder for {@link NotificationRowComponent}. @@ -46,14 +39,6 @@ public interface NotificationRowComponent { } /** - * Scope annotation for singleton items within the StatusBarComponent. - */ - @Documented - @Retention(RUNTIME) - @Scope - @interface NotificationRowScope {} - - /** * Creates a ActivatableNotificationViewController. */ @NotificationRowScope diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java new file mode 100644 index 000000000000..4555b839a3f2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationRowScope.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Scope; + +/** + * Scope annotation for singleton items within the StatusBarComponent. + */ +@Documented +@Retention(RUNTIME) +@Scope +public @interface NotificationRowScope {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java index 830b50e35490..8231f8b3a09b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java @@ -30,5 +30,6 @@ public interface HotspotController extends CallbackController<Callback>, Dumpabl interface Callback { void onHotspotChanged(boolean enabled, int numDevices); + default void onHotspotAvailabilityChanged(boolean available) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java index df9c3f4d6e26..d0904049d85a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.policy; import android.app.ActivityManager; import android.content.Context; import android.net.ConnectivityManager; +import android.net.TetheringManager; import android.net.wifi.WifiClient; import android.net.wifi.WifiManager; import android.os.Handler; @@ -26,6 +27,8 @@ import android.os.HandlerExecutor; import android.os.UserManager; import android.util.Log; +import com.android.internal.util.ConcurrentUtils; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import java.io.FileDescriptor; @@ -46,36 +49,63 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ArrayList<Callback> mCallbacks = new ArrayList<>(); - private final ConnectivityManager mConnectivityManager; + private final TetheringManager mTetheringManager; private final WifiManager mWifiManager; private final Handler mMainHandler; private final Context mContext; private int mHotspotState; private volatile int mNumConnectedDevices; + private volatile boolean mIsTetheringSupported; + private volatile boolean mHasTetherableWifiRegexs; private boolean mWaitingForTerminalState; + private TetheringManager.TetheringEventCallback mTetheringCallback = + new TetheringManager.TetheringEventCallback() { + @Override + public void onTetheringSupported(boolean supported) { + super.onTetheringSupported(supported); + if (mIsTetheringSupported != supported) { + mIsTetheringSupported = supported; + fireHotspotAvailabilityChanged(); + } + } + + @Override + public void onTetherableInterfaceRegexpsChanged( + TetheringManager.TetheringInterfaceRegexps reg) { + super.onTetherableInterfaceRegexpsChanged(reg); + final boolean newValue = reg.getTetherableWifiRegexs().size() != 0; + if (mHasTetherableWifiRegexs != newValue) { + mHasTetherableWifiRegexs = newValue; + fireHotspotAvailabilityChanged(); + } + } + }; + /** * Controller used to retrieve information related to a hotspot. */ @Inject - public HotspotControllerImpl(Context context, @Main Handler mainHandler) { + public HotspotControllerImpl(Context context, @Main Handler mainHandler, + @Background Handler backgroundHandler) { mContext = context; - mConnectivityManager = - (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mTetheringManager = context.getSystemService(TetheringManager.class); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mMainHandler = mainHandler; + mTetheringManager.registerTetheringEventCallback( + new HandlerExecutor(backgroundHandler), mTetheringCallback); } @Override public boolean isHotspotSupported() { - return mConnectivityManager.isTetheringSupported() - && mConnectivityManager.getTetherableWifiRegexs().length != 0 + return mIsTetheringSupported && mHasTetherableWifiRegexs && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser()); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("HotspotController state:"); + pw.print(" available="); pw.println(isHotspotSupported()); pw.print(" mHotspotState="); pw.println(stateToString(mHotspotState)); pw.print(" mNumConnectedDevices="); pw.println(mNumConnectedDevices); pw.print(" mWaitingForTerminalState="); pw.println(mWaitingForTerminalState); @@ -152,17 +182,18 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof if (enabled) { mWaitingForTerminalState = true; if (DEBUG) Log.d(TAG, "Starting tethering"); - mConnectivityManager.startTethering(ConnectivityManager.TETHERING_WIFI, false, - new ConnectivityManager.OnStartTetheringCallback() { + mTetheringManager.startTethering(ConnectivityManager.TETHERING_WIFI, + ConcurrentUtils.DIRECT_EXECUTOR, + new TetheringManager.StartTetheringCallback() { @Override - public void onTetheringFailed() { + public void onTetheringFailed(final int result) { if (DEBUG) Log.d(TAG, "onTetheringFailed"); maybeResetSoftApState(); fireHotspotChangedCallback(); } }); } else { - mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI); + mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI); } } @@ -177,10 +208,25 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof * (as it can be blocked). */ private void fireHotspotChangedCallback() { + List<Callback> list; synchronized (mCallbacks) { - for (Callback callback : mCallbacks) { - callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices); - } + list = new ArrayList<>(mCallbacks); + } + for (Callback callback : list) { + callback.onHotspotChanged(isHotspotEnabled(), mNumConnectedDevices); + } + } + + /** + * Sends a hotspot available changed callback. + */ + private void fireHotspotAvailabilityChanged() { + List<Callback> list; + synchronized (mCallbacks) { + list = new ArrayList<>(mCallbacks); + } + for (Callback callback : list) { + callback.onHotspotAvailabilityChanged(isHotspotSupported()); } } @@ -206,7 +252,7 @@ public class HotspotControllerImpl implements HotspotController, WifiManager.Sof switch (mHotspotState) { case WifiManager.WIFI_AP_STATE_FAILED: // TODO(b/110697252): must be called to reset soft ap state after failure - mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI); + mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI); // Fall through case WifiManager.WIFI_AP_STATE_ENABLED: case WifiManager.WIFI_AP_STATE_DISABLED: diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index e3bcdc8b0b60..751217f03fa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -225,7 +226,7 @@ class ControlsControllerImplTest : SysuiTestCase() { val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) val control = builderFromInfo(newControlInfo).build() - controller.loadForComponent(TEST_COMPONENT) {} + controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit } reset(persistenceWrapper) verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -293,11 +294,13 @@ class ControlsControllerImplTest : SysuiTestCase() { var loaded = false val control = builderFromInfo(TEST_CONTROL_INFO).build() - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(1, it.size) - val controlStatus = it[0] + assertEquals(1, controls.size) + val controlStatus = controls[0] assertEquals(ControlStatus(control, false), controlStatus) + + assertTrue(favorites.isEmpty()) } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -315,14 +318,17 @@ class ControlsControllerImplTest : SysuiTestCase() { val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build() controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(2, it.size) - val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID } + assertEquals(2, controls.size) + val controlStatus = controls.first { it.control.controlId == TEST_CONTROL_ID } assertEquals(ControlStatus(control, true), controlStatus) - val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 } + val controlStatus2 = controls.first { it.control.controlId == TEST_CONTROL_ID_2 } assertEquals(ControlStatus(control2, false), controlStatus2) + + assertEquals(1, favorites.size) + assertEquals(TEST_CONTROL_ID, favorites[0]) } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -338,13 +344,16 @@ class ControlsControllerImplTest : SysuiTestCase() { var loaded = false controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(1, it.size) - val controlStatus = it[0] + assertEquals(1, controls.size) + val controlStatus = controls[0] assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId) assertTrue(controlStatus.favorite) assertTrue(controlStatus.removed) + + assertEquals(1, favorites.size) + assertEquals(TEST_CONTROL_ID, favorites[0]) } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -361,7 +370,7 @@ class ControlsControllerImplTest : SysuiTestCase() { val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) val control = builderFromInfo(newControlInfo).build() - controller.loadForComponent(TEST_COMPONENT) {} + controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) @@ -483,4 +492,81 @@ class ControlsControllerImplTest : SysuiTestCase() { assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT_2)) } + + @Test + fun testGetFavoritesForComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testGetFavoritesForComponent_otherComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true) + assertTrue(controller.getFavoritesForComponent(TEST_COMPONENT).isEmpty()) + } + + @Test + fun testGetFavoritesForComponent_multipleInOrder() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + controller.changeFavoriteStatus(controlInfo, true) + + assertEquals(listOf(TEST_CONTROL_INFO, controlInfo), + controller.getFavoritesForComponent(TEST_COMPONENT)) + + controller.clearFavorites() + + controller.changeFavoriteStatus(controlInfo, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + assertEquals(listOf(controlInfo, TEST_CONTROL_INFO), + controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_noFavorites() { + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_differentComponentsAreFilteredOut() { + controller.replaceFavoritesForComponent(TEST_COMPONENT, + listOf(TEST_CONTROL_INFO, TEST_CONTROL_INFO_2)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_oldFavoritesRemoved() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + assertNotEquals(TEST_CONTROL_INFO, controlInfo) + + controller.changeFavoriteStatus(controlInfo, true) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_favoritesInOrder() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + + val listOrder1 = listOf(TEST_CONTROL_INFO, controlInfo) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder1) + + assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOrder1, controller.getFavoritesForComponent(TEST_COMPONENT)) + + val listOrder2 = listOf(controlInfo, TEST_CONTROL_INFO) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder2) + + assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOrder2, controller.getFavoritesForComponent(TEST_COMPONENT)) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt new file mode 100644 index 000000000000..9ffc29e0eb7e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt @@ -0,0 +1,198 @@ +/* + * 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.controls.management + +import android.app.PendingIntent +import android.service.controls.Control +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlStatus +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +open class FavoriteModelTest : SysuiTestCase() { + + @Mock + lateinit var pendingIntent: PendingIntent + @Mock + lateinit var allAdapter: ControlAdapter + @Mock + lateinit var favoritesAdapter: ControlAdapter + + val idPrefix = "controlId" + val favoritesIndices = listOf(7, 3, 1, 9) + val favoritesList = favoritesIndices.map { "controlId$it" } + lateinit var controls: List<ControlStatus> + + lateinit var model: FavoriteModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + // controlId0 --> zone = 0 + // controlId1 --> zone = 1, favorite + // controlId2 --> zone = 2 + // controlId3 --> zone = 0, favorite + // controlId4 --> zone = 1 + // controlId5 --> zone = 2 + // controlId6 --> zone = 0 + // controlId7 --> zone = 1, favorite + // controlId8 --> zone = 2 + // controlId9 --> zone = 0, favorite + controls = (0..9).map { + ControlStatus( + Control.StatelessBuilder("$idPrefix$it", pendingIntent) + .setZone((it % 3).toString()) + .build(), + it in favoritesIndices + ) + } + + model = FavoriteModel(controls, favoritesList, favoritesAdapter, allAdapter) + } +} + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class FavoriteModelNonParametrizedTests : FavoriteModelTest() { + @Test + fun testAll() { + // Zones are sorted alphabetically + val expected = listOf( + ZoneNameWrapper("0"), + ControlWrapper(controls[0]), + ControlWrapper(controls[3]), + ControlWrapper(controls[6]), + ControlWrapper(controls[9]), + ZoneNameWrapper("1"), + ControlWrapper(controls[1]), + ControlWrapper(controls[4]), + ControlWrapper(controls[7]), + ZoneNameWrapper("2"), + ControlWrapper(controls[2]), + ControlWrapper(controls[5]), + ControlWrapper(controls[8]) + ) + assertEquals(expected, model.all) + } + + @Test + fun testFavoritesInOrder() { + val expected = favoritesIndices.map { ControlWrapper(controls[it]) } + assertEquals(expected, model.favorites) + } + + @Test + fun testChangeFavoriteStatus_addFavorite() { + val controlToAdd = 6 + model.changeFavoriteStatus("$idPrefix$controlToAdd", true) + + val pair = model.all.findControl(controlToAdd) + pair?.let { + assertTrue(it.second.favorite) + assertEquals(it.second, model.favorites.last().controlStatus) + verify(favoritesAdapter).notifyItemInserted(model.favorites.size - 1) + verify(allAdapter).notifyItemChanged(it.first) + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } ?: run { + fail("control not found") + } + } + + @Test + fun testChangeFavoriteStatus_removeFavorite() { + val controlToRemove = 3 + model.changeFavoriteStatus("$idPrefix$controlToRemove", false) + + val pair = model.all.findControl(controlToRemove) + pair?.let { + assertFalse(it.second.favorite) + assertTrue(model.favorites.none { + it.controlStatus.control.controlId == "$idPrefix$controlToRemove" + }) + verify(favoritesAdapter).notifyItemRemoved(favoritesIndices.indexOf(controlToRemove)) + verify(allAdapter).notifyItemChanged(it.first) + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } ?: run { + fail("control not found") + } + } + + @Test + fun testChangeFavoriteStatus_sameStatus() { + model.changeFavoriteStatus("${idPrefix}7", true) + model.changeFavoriteStatus("${idPrefix}6", false) + + val expected = favoritesIndices.map { ControlWrapper(controls[it]) } + assertEquals(expected, model.favorites) + + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } + + private fun List<ElementWrapper>.findControl(controlIndex: Int): Pair<Int, ControlStatus>? { + val index = indexOfFirst { + it is ControlWrapper && + it.controlStatus.control.controlId == "$idPrefix$controlIndex" + } + return if (index == -1) null else index to (get(index) as ControlWrapper).controlStatus + } +} + +@SmallTest +@RunWith(Parameterized::class) +class FavoriteModelParameterizedTest(val from: Int, val to: Int) : FavoriteModelTest() { + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0} -> {1}") + fun data(): Collection<Array<Int>> { + return (0..3).flatMap { from -> + (0..3).map { to -> + arrayOf(from, to) + } + }.filterNot { it[0] == it[1] } + } + } + + @Test + fun testMoveItem() { + val originalFavorites = model.favorites.toList() + val originalFavoritesIds = + model.favorites.map { it.controlStatus.control.controlId }.toSet() + model.onMoveItem(from, to) + assertEquals(originalFavorites[from], model.favorites[to]) + // Check that we still have the same favorites + assertEquals(originalFavoritesIds, + model.favorites.map { it.controlStatus.control.controlId }.toSet()) + + verify(favoritesAdapter).notifyItemMoved(from, to) + + verifyNoMoreInteractions(allAdapter, favoritesAdapter) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java index a5722e211f98..4b47093bb951 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java @@ -16,52 +16,116 @@ package com.android.systemui.glwallpaper; -import static org.junit.Assert.*; -import static org.mockito.Mockito.RETURNS_DEFAULTS; -import static org.mockito.Mockito.mock; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atMost; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.graphics.PixelFormat; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; +import android.view.Surface; +import android.view.SurfaceControl; import android.view.SurfaceHolder; +import android.view.SurfaceSession; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; - +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper public class EglHelperTest extends SysuiTestCase { - @Mock + @Spy private EglHelper mEglHelper; + @Mock private SurfaceHolder mSurfaceHolder; @Before public void setUp() throws Exception { - mEglHelper = mock(EglHelper.class, RETURNS_DEFAULTS); - mSurfaceHolder = mock(SurfaceHolder.class, RETURNS_DEFAULTS); + MockitoAnnotations.initMocks(this); + prepareSurface(); + } + + @After + public void tearDown() { + mSurfaceHolder.getSurface().destroy(); + mSurfaceHolder = null; + } + + private void prepareSurface() { + final SurfaceSession session = new SurfaceSession(); + final SurfaceControl control = new SurfaceControl.Builder(session) + .setName("Test") + .setBufferSize(100, 100) + .setFormat(PixelFormat.RGB_888) + .build(); + final Surface surface = new Surface(); + surface.copyFrom(control); + when(mSurfaceHolder.getSurface()).thenReturn(surface); + assertThat(mSurfaceHolder.getSurface()).isNotNull(); + assertThat(mSurfaceHolder.getSurface().isValid()).isTrue(); } @Test public void testInit_finish() { mEglHelper.init(mSurfaceHolder, false /* wideColorGamut */); + assertThat(mEglHelper.hasEglDisplay()).isTrue(); + assertThat(mEglHelper.hasEglContext()).isTrue(); + assertThat(mEglHelper.hasEglSurface()).isTrue(); + verify(mEglHelper).askCreatingEglWindowSurface( + any(SurfaceHolder.class), eq(null), anyInt()); + + mEglHelper.finish(); + assertThat(mEglHelper.hasEglSurface()).isFalse(); + assertThat(mEglHelper.hasEglContext()).isFalse(); + assertThat(mEglHelper.hasEglDisplay()).isFalse(); + } + + @Test + public void testInit_finish_wide_gamut() { + // In EglHelper, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490; + doReturn(0x3490).when(mEglHelper).getWcgCapability(); + // In EglHelper, KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace"; + doReturn(true).when(mEglHelper).checkExtensionCapability("EGL_KHR_gl_colorspace"); + ArgumentCaptor<int[]> ac = ArgumentCaptor.forClass(int[].class); + // {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT, EGL_NONE} + final int[] expectedArgument = new int[] {0x309D, 0x3490, 0x3038}; + + mEglHelper.init(mSurfaceHolder, true /* wideColorGamut */); + verify(mEglHelper) + .askCreatingEglWindowSurface(any(SurfaceHolder.class), ac.capture(), anyInt()); + assertThat(ac.getValue()).isNotNull(); + assertThat(ac.getValue()).isEqualTo(expectedArgument); mEglHelper.finish(); } @Test public void testFinish_shouldNotCrash() { - assertFalse(mEglHelper.hasEglDisplay()); - assertFalse(mEglHelper.hasEglSurface()); - assertFalse(mEglHelper.hasEglContext()); + mEglHelper.terminateEglDisplay(); + assertThat(mEglHelper.hasEglDisplay()).isFalse(); + assertThat(mEglHelper.hasEglSurface()).isFalse(); + assertThat(mEglHelper.hasEglContext()).isFalse(); mEglHelper.finish(); + verify(mEglHelper, never()).destroyEglContext(); + verify(mEglHelper, never()).destroyEglSurface(); + verify(mEglHelper, atMost(1)).terminateEglDisplay(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java new file mode 100644 index 000000000000..d881fd51712d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/ImageWallpaperRendererTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.glwallpaper; + +import static com.android.systemui.glwallpaper.GLWallpaperRenderer.SurfaceProxy; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.app.WallpaperManager; +import android.app.WallpaperManager.ColorManagementProxy; +import android.graphics.Bitmap; +import android.graphics.ColorSpace; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class ImageWallpaperRendererTest extends SysuiTestCase { + + private WallpaperManager mWpmSpy; + private SurfaceProxy mSurfaceProxy; + + @Before + public void setUp() throws Exception { + final WallpaperManager wpm = mContext.getSystemService(WallpaperManager.class); + mWpmSpy = spy(wpm); + mContext.addMockSystemService(WallpaperManager.class, mWpmSpy); + + mSurfaceProxy = new SurfaceProxy() { + @Override + public void requestRender() { + // NO-op + } + + @Override + public void preRender() { + // No-op + } + + @Override + public void postRender() { + // No-op + } + }; + } + + @Test + public void testWcgContent() throws IOException { + final Bitmap srgbBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); + final Bitmap p3Bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888, + false /* hasAlpha */, ColorSpace.get(ColorSpace.Named.DISPLAY_P3)); + + final ColorManagementProxy proxy = new ColorManagementProxy(mContext); + final ColorManagementProxy cmProxySpy = spy(proxy); + final Set<ColorSpace> supportedWideGamuts = new HashSet<>(); + supportedWideGamuts.add(ColorSpace.get(ColorSpace.Named.DISPLAY_P3)); + + try { + doReturn(true).when(mWpmSpy).shouldEnableWideColorGamut(); + doReturn(cmProxySpy).when(mWpmSpy).getColorManagementProxy(); + doReturn(supportedWideGamuts).when(cmProxySpy).getSupportedColorSpaces(); + + mWpmSpy.setBitmap(p3Bitmap); + ImageWallpaperRenderer rendererP3 = new ImageWallpaperRenderer(mContext, mSurfaceProxy); + assertThat(rendererP3.isWcgContent()).isTrue(); + + mWpmSpy.setBitmap(srgbBitmap); + ImageWallpaperRenderer renderer = new ImageWallpaperRenderer(mContext, mSurfaceProxy); + assertThat(renderer.isWcgContent()).isFalse(); + } finally { + srgbBitmap.recycle(); + p3Bitmap.recycle(); + } + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 07f6936ece07..5a0a495e1f85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -58,10 +58,13 @@ import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.NotificationVisibility; +import com.android.internal.util.NotificationMessagingUtil; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -83,12 +86,13 @@ import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotifBindPipeline; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.RowContentBindParams; import com.android.systemui.statusbar.notification.row.RowContentBindStage; import com.android.systemui.statusbar.notification.row.RowInflaterTask; -import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; +import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; @@ -96,6 +100,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.Assert; import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; @@ -106,6 +111,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; @@ -141,8 +147,13 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private NotificationEntryManagerLogger mLogger; @Mock private FeatureFlags mFeatureFlags; @Mock private LeakDetector mLeakDetector; - @Mock private ActivatableNotificationViewController mActivatableNotificationViewController; - @Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder; + @Mock private NotificationMediaManager mNotificationMediaManager; + @Mock private ExpandableNotificationRowComponent.Builder + mExpandableNotificationRowComponentBuilder; + @Mock private ExpandableNotificationRowComponent mExpandableNotificationRowComponent; + @Mock private FalsingManager mFalsingManager; + @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock private StatusBarStateController mStatusBarStateController; private int mId; private NotificationEntry mEntry; @@ -191,7 +202,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mDependency.injectMockDependency(SmartReplyController.class); - mDependency.injectMockDependency(NotificationMediaManager.class); mCountDownLatch = new CountDownLatch(1); @@ -207,28 +217,23 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); - when(mNotificationRowComponentBuilder.activatableNotificationView(any())) - .thenReturn(mNotificationRowComponentBuilder); - when(mNotificationRowComponentBuilder.build()).thenReturn( - () -> mActivatableNotificationViewController); - RowContentBindStage bindStage = mock(RowContentBindStage.class); when(bindStage.getStageParams(any())).thenReturn(new RowContentBindParams()); - NotificationRowBinderImpl notificationRowBinder = new NotificationRowBinderImpl(mContext, + new NotificationMessagingUtil(mContext), mRemoteInputManager, mLockscreenUserManager, mock(NotifBindPipeline.class), bindStage, true, /* allowLongPress */ - mock(KeyguardBypassController.class), - mock(StatusBarStateController.class), + mKeyguardBypassController, + mStatusBarStateController, mGroupManager, mGutsManager, mNotificationInterruptionStateProvider, - () -> new RowInflaterTask(mNotificationRowComponentBuilder), - mock(NotificationLogger.class)); + RowInflaterTask::new, + mExpandableNotificationRowComponentBuilder); when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false); when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false); @@ -236,7 +241,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mLogger, mGroupManager, new NotificationRankingManager( - () -> mock(NotificationMediaManager.class), + () -> mNotificationMediaManager, mGroupManager, mHeadsUpManager, mock(NotificationFilter.class), @@ -255,13 +260,55 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntryManager.addNotificationEntryListener(mEntryListener); mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor); - notificationRowBinder.setUpWithPresenter( - mPresenter, mListContainer, mHeadsUpManager, mBindCallback); + notificationRowBinder.setUpWithPresenter(mPresenter, mListContainer, mBindCallback); notificationRowBinder.setInflationCallback(mEntryManager); notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class)); setUserSentiment( mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL); + + ArgumentCaptor<ExpandableNotificationRow> viewCaptor = + ArgumentCaptor.forClass(ExpandableNotificationRow.class); + when(mExpandableNotificationRowComponentBuilder + .expandableNotificationRow(viewCaptor.capture())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .notificationEntry(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .onDismissRunnable(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .inflationCallback(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + when(mExpandableNotificationRowComponentBuilder + .onExpandClickListener(any())) + .thenReturn(mExpandableNotificationRowComponentBuilder); + + when(mExpandableNotificationRowComponentBuilder.build()) + .thenReturn(mExpandableNotificationRowComponent); + when(mExpandableNotificationRowComponent.getExpandableNotificationRowController()) + .thenAnswer((Answer<ExpandableNotificationRowController>) invocation -> + new ExpandableNotificationRowController( + viewCaptor.getValue(), + mock(ActivatableNotificationViewController.class), + mNotificationMediaManager, + mock(PluginManager.class), + new FakeSystemClock(), + "FOOBAR", "FOOBAR", + mKeyguardBypassController, + mGroupManager, + bindStage, + mock(NotificationLogger.class), + mHeadsUpManager, + mPresenter, + mStatusBarStateController, + mEntryManager, + mGutsManager, + true, + null, + mFalsingManager + )); } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index d8cf6ed9a47b..a8918103c4a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -194,9 +194,8 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test public void testClickSound() throws Exception { assertTrue("Should play sounds by default.", mGroupRow.isSoundEffectsEnabled()); - StatusBarStateController mock = mock(StatusBarStateController.class); + StatusBarStateController mock = mNotificationTestHelper.getStatusBarStateController(); when(mock.isDozing()).thenReturn(true); - mGroupRow.setStatusBarStateController(mock); mGroupRow.setSecureStateProvider(()-> false); assertFalse("Shouldn't play sounds when dark and trusted.", mGroupRow.isSoundEffectsEnabled()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index f080d67bfffb..e8de10f7392b 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 @@ -75,6 +75,7 @@ import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.phone.ShadeController; import org.junit.Before; import org.junit.Rule; @@ -131,6 +132,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { private ShortcutManager mShortcutManager; @Mock private NotificationGuts mNotificationGuts; + @Mock + private ShadeController mShadeController; @Before public void setUp() throws Exception { @@ -139,12 +142,12 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); mDependency.injectTestDependency(BubbleController.class, mBubbleController); + mDependency.injectTestDependency(ShadeController.class, mShadeController); // Inflate the layout final LayoutInflater layoutInflater = LayoutInflater.from(mContext); mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate( R.layout.notification_conversation_info, null); - mNotificationInfo.mShowHomeScreen = true; mNotificationInfo.setGutsParent(mNotificationGuts); doAnswer((Answer<Object>) invocation -> { mNotificationInfo.handleCloseControls(true, false); @@ -173,7 +176,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { when(mShortcutInfo.getShortLabel()).thenReturn("Convo name"); List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo); when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); - mImage = mContext.getDrawable(R.drawable.ic_star); + mImage = mContext.getDrawable(R.drawable.ic_remove); when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo), anyInt())).thenReturn(mImage); @@ -333,8 +336,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); - final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); - assertEquals(GONE, dividerView.getVisibility()); } @Test @@ -364,8 +365,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); assertTrue(nameView.getText().toString().contains("Proxied")); - final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); - assertEquals(VISIBLE, dividerView.getVisibility()); } @Test @@ -502,6 +501,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), anyInt(), any()); + verify(mShadeController).animateCollapsePanels(); } @Test @@ -644,9 +644,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); - Button fave = mNotificationInfo.findViewById(R.id.fave); - assertEquals(mContext.getString(R.string.notification_conversation_favorite), - fave.getText().toString()); + ImageButton fave = mNotificationInfo.findViewById(R.id.fave); + assertEquals(mContext.getString(R.string.notification_conversation_unfavorite), + fave.getContentDescription().toString()); fave.performClick(); mTestableLooper.processAllMessages(); @@ -677,9 +677,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, true); - Button fave = mNotificationInfo.findViewById(R.id.fave); - assertEquals(mContext.getString(R.string.notification_conversation_unfavorite), - fave.getText().toString()); + ImageButton fave = mNotificationInfo.findViewById(R.id.fave); + assertEquals(mContext.getString(R.string.notification_conversation_favorite), + fave.getContentDescription().toString()); fave.performClick(); mTestableLooper.processAllMessages(); @@ -692,34 +692,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { } @Test - public void testDemote() throws Exception { - mNotificationInfo.bindNotification( - mShortcutManager, - mLauncherApps, - mMockPackageManager, - mMockINotificationManager, - mVisualStabilityManager, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - true); - - - ImageButton demote = mNotificationInfo.findViewById(R.id.demote); - demote.performClick(); - mTestableLooper.processAllMessages(); - - ArgumentCaptor<NotificationChannel> captor = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), anyInt(), captor.capture()); - assertTrue(captor.getValue().isDemoted()); - } - - @Test public void testMute_mute() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mConversationChannel.setImportance(IMPORTANCE_DEFAULT); @@ -738,9 +710,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, true); - Button mute = mNotificationInfo.findViewById(R.id.mute); - assertEquals(mContext.getString(R.string.notification_conversation_mute), - mute.getText().toString()); + ImageButton mute = mNotificationInfo.findViewById(R.id.mute); + assertEquals(mContext.getString(R.string.notification_conversation_unmute), + mute.getContentDescription().toString()); mute.performClick(); mTestableLooper.processAllMessages(); @@ -774,9 +746,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); - Button mute = mNotificationInfo.findViewById(R.id.mute); - assertEquals(mContext.getString(R.string.notification_conversation_unmute), - mute.getText().toString()); + ImageButton mute = mNotificationInfo.findViewById(R.id.mute); + assertEquals(mContext.getString(R.string.notification_conversation_mute), + mute.getContentDescription().toString()); mute.performClick(); mTestableLooper.processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 9b2e0c375e87..35b55087873b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -45,6 +45,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubblesTestActivity; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -91,6 +92,7 @@ public class NotificationTestHelper { private final NotifBindPipeline mBindPipeline; private final NotificationEntryListener mBindPipelineEntryListener; private final RowContentBindStage mBindStage; + private StatusBarStateController mStatusBarStateController; public NotificationTestHelper(Context context, TestableDependency dependency) { mContext = context; @@ -98,9 +100,9 @@ public class NotificationTestHelper { dependency.injectMockDependency(BubbleController.class); dependency.injectMockDependency(NotificationShadeWindowController.class); dependency.injectMockDependency(SmartReplyController.class); - StatusBarStateController stateController = mock(StatusBarStateController.class); - mGroupManager = new NotificationGroupManager(stateController); - mHeadsUpManager = new HeadsUpManagerPhone(mContext, stateController, + mStatusBarStateController = mock(StatusBarStateController.class); + mGroupManager = new NotificationGroupManager(mStatusBarStateController); + mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController, mock(KeyguardBypassController.class)); mHeadsUpManager.setUp(null, mGroupManager, null, null); mGroupManager.setHeadsUpManager(mHeadsUpManager); @@ -321,6 +323,10 @@ public class NotificationTestHelper { return notificationBuilder.build(); } + public StatusBarStateController getStatusBarStateController() { + return mStatusBarStateController; + } + private ExpandableNotificationRow generateRow( Notification notification, String pkg, @@ -382,7 +388,11 @@ public class NotificationTestHelper { mGroupManager, mHeadsUpManager, mBindStage, - mock(OnExpandClickListener.class)); + mock(OnExpandClickListener.class), + mock(NotificationMediaManager.class), + mock(ExpandableNotificationRow.OnAppOpsClickListener.class), + mock(FalsingManager.class), + mStatusBarStateController); row.setAboveShelfChangedListener(aboveShelf -> { }); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); inflateAndWait(entry, mBindStage); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index e84f14a6a2c1..2d1bc7890aed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -30,7 +30,6 @@ import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -59,8 +58,6 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { private ExpandableNotificationRow mFirst; private ExpandableNotificationRow mSecond; @Mock - private StatusBarStateController mStatusBarStateController; - @Mock private KeyguardBypassController mBypassController; @Before @@ -150,13 +147,12 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, mSecond), createSection(null, null) }); - ExpandableNotificationRow row = new NotificationTestHelper(getContext(), mDependency) - .createRow(); + NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency); + ExpandableNotificationRow row = testHelper.createRow(); NotificationEntry entry = mock(NotificationEntry.class); when(entry.getRow()).thenReturn(row); - when(mStatusBarStateController.isDozing()).thenReturn(true); - row.setStatusBarStateController(mStatusBarStateController); + when(testHelper.getStatusBarStateController().isDozing()).thenReturn(true); row.setHeadsUp(true); mRoundnessManager.onHeadsUpStateChanged(entry, true); Assert.assertEquals(1f, row.getCurrentBottomRoundness(), 0.0f); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java index 631c580a490d..cd91f22bb753 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -24,10 +26,12 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -import android.net.ConnectivityManager; +import android.net.TetheringManager; import android.net.wifi.WifiManager; import android.os.Handler; +import android.os.UserManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -38,11 +42,14 @@ import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import java.util.ArrayList; +import java.util.Collections; import java.util.concurrent.Executor; @SmallTest @@ -51,13 +58,19 @@ import java.util.concurrent.Executor; public class HotspotControllerImplTest extends SysuiTestCase { @Mock - private ConnectivityManager mConnectivityManager; + private TetheringManager mTetheringManager; @Mock private WifiManager mWifiManager; @Mock + private UserManager mUserManager; + @Mock private HotspotController.Callback mCallback1; @Mock private HotspotController.Callback mCallback2; + @Mock + private TetheringManager.TetheringInterfaceRegexps mTetheringInterfaceRegexps; + @Captor + private ArgumentCaptor<TetheringManager.TetheringEventCallback> mTetheringCallbackCaptor; private HotspotControllerImpl mController; private TestableLooper mLooper; @@ -66,8 +79,13 @@ public class HotspotControllerImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mLooper = TestableLooper.get(this); - mContext.addMockSystemService(ConnectivityManager.class, mConnectivityManager); mContext.addMockSystemService(WifiManager.class, mWifiManager); + mContext.addMockSystemService(TetheringManager.class, mTetheringManager); + mContext.addMockSystemService(UserManager.class, mUserManager); + + when(mUserManager.isUserAdmin(anyInt())).thenReturn(true); + when(mTetheringInterfaceRegexps.getTetherableWifiRegexs()).thenReturn( + Collections.singletonList("test")); doAnswer((InvocationOnMock invocation) -> { ((WifiManager.SoftApCallback) invocation.getArgument(1)) @@ -76,7 +94,11 @@ public class HotspotControllerImplTest extends SysuiTestCase { }).when(mWifiManager).registerSoftApCallback(any(Executor.class), any(WifiManager.SoftApCallback.class)); - mController = new HotspotControllerImpl(mContext, new Handler(mLooper.getLooper())); + Handler handler = new Handler(mLooper.getLooper()); + + mController = new HotspotControllerImpl(mContext, handler, handler); + verify(mTetheringManager) + .registerTetheringEventCallback(any(), mTetheringCallbackCaptor.capture()); } @Test @@ -117,4 +139,28 @@ public class HotspotControllerImplTest extends SysuiTestCase { verify(mWifiManager, never()).unregisterSoftApCallback(any()); } + @Test + public void testDefault_hotspotNotSupported() { + assertFalse(mController.isHotspotSupported()); + } + + @Test + public void testHotspotSupported_rightConditions() { + mTetheringCallbackCaptor.getValue().onTetheringSupported(true); + mTetheringCallbackCaptor.getValue() + .onTetherableInterfaceRegexpsChanged(mTetheringInterfaceRegexps); + + assertTrue(mController.isHotspotSupported()); + } + + @Test + public void testHotspotSupported_callbackCalledOnChange() { + mController.addCallback(mCallback1); + mTetheringCallbackCaptor.getValue().onTetheringSupported(true); + mTetheringCallbackCaptor.getValue() + .onTetherableInterfaceRegexpsChanged(mTetheringInterfaceRegexps); + + verify(mCallback1).onHotspotAvailabilityChanged(true); + } + } diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index cf2b1f06d769..4efe93439b42 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -28,6 +28,7 @@ java_defaults { "netd_aidl_interface-unstable-java", "netlink-client", "networkstack-aidl-interfaces-unstable-java", + "android.hardware.tetheroffload.config-V1.0-java", "android.hardware.tetheroffload.control-V1.0-java", "net-utils-framework-common", ], @@ -48,26 +49,26 @@ android_library { // Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK). cc_library { name: "libtetherutilsjni", + sdk_version: "current", srcs: [ "jni/android_net_util_TetheringUtils.cpp", ], shared_libs: [ - "libcgrouprc", - "libnativehelper_compat_libc++", - "libvndksupport", - ], - static_libs: [ - "android.hardware.tetheroffload.config@1.0", "liblog", - "libbase", - "libbinderthreadstate", - "libcutils", - "libhidlbase", - "libjsoncpp", - "libprocessgroup", - "libutils", + "libnativehelper_compat_libc++", ], + // We cannot use plain "libc++" here to link libc++ dynamically because it results in: + // java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found + // even if "libc++" is added into jni_libs below. Adding "libc++_shared" into jni_libs doesn't + // build because soong complains of: + // module Tethering missing dependencies: libc++_shared + // + // So, link libc++ statically. This means that we also need to ensure that all the C++ libraries + // we depend on do not dynamically link libc++. This is currently the case, because liblog is + // C-only and libnativehelper_compat_libc also uses stl: "c++_static". + stl: "c++_static", + cflags: [ "-Wall", "-Werror", @@ -86,9 +87,8 @@ java_defaults { // Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies // explicitly. jni_libs: [ - "libcgrouprc", + "liblog", "libnativehelper_compat_libc++", - "libvndksupport", "libtetherutilsjni", ], resource_dirs: [ diff --git a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp index 1cf8f988432c..549344064405 100644 --- a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp +++ b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp @@ -16,123 +16,18 @@ #include <errno.h> #include <error.h> -#include <hidl/HidlSupport.h> #include <jni.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> -#include <linux/netfilter/nfnetlink.h> -#include <linux/netlink.h> #include <net/if.h> #include <netinet/icmp6.h> #include <sys/socket.h> -#include <android-base/unique_fd.h> -#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h> #define LOG_TAG "TetheringUtils" -#include <utils/Log.h> +#include <android/log.h> namespace android { -using hardware::hidl_handle; -using hardware::hidl_string; -using hardware::tetheroffload::config::V1_0::IOffloadConfig; - -namespace { - -inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) { - return reinterpret_cast<const sockaddr *>(nladdr); -} - -int conntrackSocket(unsigned groups) { - base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER)); - if (s.get() < 0) return -errno; - - const struct sockaddr_nl bind_addr = { - .nl_family = AF_NETLINK, - .nl_pad = 0, - .nl_pid = 0, - .nl_groups = groups, - }; - if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) { - return -errno; - } - - const struct sockaddr_nl kernel_addr = { - .nl_family = AF_NETLINK, - .nl_pad = 0, - .nl_pid = 0, - .nl_groups = groups, - }; - if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) { - return -errno; - } - - return s.release(); -} - -// Return a hidl_handle that owns the file descriptor owned by fd, and will -// auto-close it (otherwise there would be double-close problems). -// -// Rely upon the compiler to eliminate the constexprs used for clarity. -hidl_handle handleFromFileDescriptor(base::unique_fd fd) { - hidl_handle h; - - static constexpr int kNumFds = 1; - static constexpr int kNumInts = 0; - native_handle_t *nh = native_handle_create(kNumFds, kNumInts); - nh->data[0] = fd.release(); - - static constexpr bool kTakeOwnership = true; - h.setTo(nh, kTakeOwnership); - - return h; -} - -} // namespace - -static jboolean android_net_util_configOffload( - JNIEnv* /* env */) { - sp<IOffloadConfig> configInterface = IOffloadConfig::getService(); - if (configInterface.get() == nullptr) { - ALOGD("Could not find IOffloadConfig service."); - return false; - } - - // Per the IConfigOffload definition: - // - // fd1 A file descriptor bound to the following netlink groups - // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY). - // - // fd2 A file descriptor bound to the following netlink groups - // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). - base::unique_fd - fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)), - fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY)); - if (fd1.get() < 0 || fd2.get() < 0) { - ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno)); - return false; - } - - hidl_handle h1(handleFromFileDescriptor(std::move(fd1))), - h2(handleFromFileDescriptor(std::move(fd2))); - - bool rval(false); - hidl_string msg; - const auto status = configInterface->setHandles(h1, h2, - [&rval, &msg](bool success, const hidl_string& errMsg) { - rval = success; - msg = errMsg; - }); - if (!status.isOk() || !rval) { - ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'", - status.description().c_str(), msg.c_str()); - // If status is somehow not ok, make sure rval captures this too. - rval = false; - } - - return rval; -} - static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd, jint ifIndex) { @@ -229,7 +124,6 @@ static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject j */ static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - { "configOffload", "()Z", (void*) android_net_util_configOffload }, { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_util_setupRaSocket }, }; @@ -242,7 +136,7 @@ int register_android_net_util_TetheringUtils(JNIEnv* env) { extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - ALOGE("ERROR: GetEnv failed"); + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed"); return JNI_ERR; } diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java index fa543bdce735..5a6d5c1cbfb0 100644 --- a/packages/Tethering/src/android/net/util/TetheringUtils.java +++ b/packages/Tethering/src/android/net/util/TetheringUtils.java @@ -24,14 +24,6 @@ import java.net.SocketException; * {@hide} */ public class TetheringUtils { - - /** - * Offload management process need to know conntrack rules to support NAT, but it may not have - * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and - * share them with offload management process. - */ - public static native boolean configOffload(); - /** * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements. * @param fd the socket's {@link FileDescriptor}. diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java index 90b9d3f148dc..b54571720857 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java @@ -18,19 +18,28 @@ package com.android.server.connectivity.tethering; import static android.net.util.TetheringUtils.uint16; +import android.hardware.tetheroffload.config.V1_0.IOffloadConfig; import android.hardware.tetheroffload.control.V1_0.IOffloadControl; import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; +import android.net.netlink.NetlinkSocket; import android.net.util.SharedLog; -import android.net.util.TetheringUtils; +import android.net.util.SocketUtils; import android.os.Handler; +import android.os.NativeHandle; import android.os.RemoteException; +import android.system.ErrnoException; +import android.system.Os; import android.system.OsConstants; import com.android.internal.annotations.VisibleForTesting; +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.SocketAddress; +import java.net.SocketException; import java.util.ArrayList; @@ -49,6 +58,10 @@ public class OffloadHardwareInterface { private static final String NO_INTERFACE_NAME = ""; private static final String NO_IPV4_ADDRESS = ""; private static final String NO_IPV4_GATEWAY = ""; + // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h + private static final int NF_NETLINK_CONNTRACK_NEW = 1; + private static final int NF_NETLINK_CONNTRACK_UPDATE = 2; + private static final int NF_NETLINK_CONNTRACK_DESTROY = 4; private final Handler mHandler; private final SharedLog mLog; @@ -121,9 +134,103 @@ public class OffloadHardwareInterface { return DEFAULT_TETHER_OFFLOAD_DISABLED; } - /** Configure offload management process. */ + /** + * Offload management process need to know conntrack rules to support NAT, but it may not have + * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and + * share them with offload management process. + */ public boolean initOffloadConfig() { - return TetheringUtils.configOffload(); + IOffloadConfig offloadConfig; + try { + offloadConfig = IOffloadConfig.getService(); + } catch (RemoteException e) { + mLog.e("getIOffloadConfig error " + e); + return false; + } + if (offloadConfig == null) { + mLog.e("Could not find IOffloadConfig service"); + return false; + } + // Per the IConfigOffload definition: + // + // h1 provides a file descriptor bound to the following netlink groups + // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY). + // + // h2 provides a file descriptor bound to the following netlink groups + // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). + final NativeHandle h1 = createConntrackSocket( + NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); + if (h1 == null) return false; + + final NativeHandle h2 = createConntrackSocket( + NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY); + if (h2 == null) { + closeFdInNativeHandle(h1); + return false; + } + + final CbResults results = new CbResults(); + try { + offloadConfig.setHandles(h1, h2, + (boolean success, String errMsg) -> { + results.mSuccess = success; + results.mErrMsg = errMsg; + }); + } catch (RemoteException e) { + record("initOffloadConfig, setHandles fail", e); + return false; + } + // Explicitly close FDs. + closeFdInNativeHandle(h1); + closeFdInNativeHandle(h2); + + record("initOffloadConfig, setHandles results:", results); + return results.mSuccess; + } + + private void closeFdInNativeHandle(final NativeHandle h) { + try { + h.close(); + } catch (IOException | IllegalStateException e) { + // IllegalStateException means fd is already closed, do nothing here. + // Also nothing we can do if IOException. + } + } + + private NativeHandle createConntrackSocket(final int groups) { + FileDescriptor fd; + try { + fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER); + } catch (ErrnoException e) { + mLog.e("Unable to create conntrack socket " + e); + return null; + } + + final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups); + try { + Os.bind(fd, sockAddr); + } catch (ErrnoException | SocketException e) { + mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ie) { + // Nothing we can do here + } + return null; + } + try { + Os.connect(fd, sockAddr); + } catch (ErrnoException | SocketException e) { + mLog.e("connect to kernel fail for groups " + groups + " error: " + e); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ie) { + // Nothing we can do here + } + return null; + } + + return new NativeHandle(fd, true); } /** Initialize the tethering offload HAL. */ diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java index c67fe9ffa916..1e8109cb393c 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java @@ -23,6 +23,8 @@ import android.util.Log; public class PacNative { private static final String TAG = "PacProxy"; + private static final PacNative sInstance = new PacNative(); + private String mCurrentPac; private boolean mIsActive; @@ -39,8 +41,12 @@ public class PacNative { System.loadLibrary("jni_pacprocessor"); } - PacNative() { + private PacNative() { + + } + public static PacNative getInstance() { + return sInstance; } public synchronized boolean startPacSupport() { diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java index 74391eb676d1..b006d6e1fa7b 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java @@ -31,43 +31,27 @@ import java.net.URL; public class PacService extends Service { private static final String TAG = "PacService"; - private PacNative mPacNative; - private ProxyServiceStub mStub; + private PacNative mPacNative = PacNative.getInstance(); + private ProxyServiceStub mStub = new ProxyServiceStub(); @Override public void onCreate() { super.onCreate(); - if (mPacNative == null) { - mPacNative = new PacNative(); - mStub = new ProxyServiceStub(mPacNative); - } + mPacNative.startPacSupport(); } @Override public void onDestroy() { + mPacNative.stopPacSupport(); super.onDestroy(); - if (mPacNative != null) { - mPacNative.stopPacSupport(); - mPacNative = null; - mStub = null; - } } @Override public IBinder onBind(Intent intent) { - if (mPacNative == null) { - mPacNative = new PacNative(); - mStub = new ProxyServiceStub(mPacNative); - } return mStub; } - private static class ProxyServiceStub extends IProxyService.Stub { - private final PacNative mPacNative; - - public ProxyServiceStub(PacNative pacNative) { - mPacNative = pacNative; - } + private class ProxyServiceStub extends IProxyService.Stub { @Override public String resolvePacFile(String host, String url) throws RemoteException { @@ -102,20 +86,12 @@ public class PacService extends Service { @Override public void startPacSystem() throws RemoteException { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - Log.e(TAG, "Only system user is allowed to call startPacSystem"); - throw new SecurityException(); - } - mPacNative.startPacSupport(); + //TODO: remove } @Override public void stopPacSystem() throws RemoteException { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { - Log.e(TAG, "Only system user is allowed to call stopPacSystem"); - throw new SecurityException(); - } - mPacNative.stopPacSupport(); + //TODO: remove } } } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 54b420191deb..63dd99e215ab 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -244,6 +244,10 @@ message SystemMessage { // Package: android NOTE_SOFTAP_AUTO_DISABLED = 58; + // Notify the user that their admin has changed location settings. + // Package: android + NOTE_LOCATION_CHANGED = 59; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/services/Android.bp b/services/Android.bp index 32394f4fe5d9..416f448a965f 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -30,6 +30,7 @@ filegroup { ":services.usb-sources", ":services.voiceinteraction-sources", ":service-permission-sources", + ":service-statsd-sources", ], visibility: ["//visibility:private"], } diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java new file mode 100644 index 000000000000..1fc48d235add --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill; + +import static com.android.server.autofill.Helper.sDebug; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.RemoteException; +import android.util.Log; +import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestionsRequest; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInlineSuggestionsResponseCallback; +import com.android.server.inputmethod.InputMethodManagerInternal; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Maintains an inline suggestion autofill session. + * + * <p> This class is thread safe. + */ +final class InlineSuggestionSession { + + private static final String TAG = "InlineSuggestionSession"; + private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; + + @NonNull + private final InputMethodManagerInternal mInputMethodManagerInternal; + private final int mUserId; + @NonNull + private final ComponentName mComponentName; + @NonNull + private final Object mLock; + + @GuardedBy("mLock") + @Nullable + private CompletableFuture<ImeResponse> mPendingImeResponse; + + InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal, + int userId, ComponentName componentName) { + mInputMethodManagerInternal = inputMethodManagerInternal; + mUserId = userId; + mComponentName = componentName; + mLock = new Object(); + } + + public void createRequest(@NonNull AutofillId currentViewId) { + synchronized (mLock) { + cancelCurrentRequest(); + mPendingImeResponse = new CompletableFuture<>(); + mInputMethodManagerInternal.onCreateInlineSuggestionsRequest( + mUserId, mComponentName, currentViewId, + new InlineSuggestionsRequestCallbackImpl(mPendingImeResponse)); + } + } + + @Nullable + public ImeResponse waitAndGetImeResponse() { + CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse(); + if (pendingImeResponse == null || pendingImeResponse.isCancelled()) { + return null; + } + try { + return pendingImeResponse.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.w(TAG, "Exception getting inline suggestions request in time: " + e); + } catch (CancellationException e) { + Log.w(TAG, "Inline suggestions request cancelled"); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + return null; + } + + private void cancelCurrentRequest() { + CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse(); + if (pendingImeResponse != null) { + pendingImeResponse.cancel(true); + } + } + + @Nullable + @GuardedBy("mLock") + private CompletableFuture<ImeResponse> getPendingImeResponse() { + synchronized (mLock) { + return mPendingImeResponse; + } + } + + private static final class InlineSuggestionsRequestCallbackImpl + extends IInlineSuggestionsRequestCallback.Stub { + + private final CompletableFuture<ImeResponse> mResponse; + + private InlineSuggestionsRequestCallbackImpl(CompletableFuture<ImeResponse> response) { + mResponse = response; + } + + @Override + public void onInlineSuggestionsUnsupported() throws RemoteException { + if (sDebug) { + Log.d(TAG, "onInlineSuggestionsUnsupported() called."); + } + mResponse.cancel(true); + } + + @Override + public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, + IInlineSuggestionsResponseCallback callback) throws RemoteException { + if (sDebug) { + Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); + } + if (request != null && callback != null) { + mResponse.complete(new ImeResponse(request, callback)); + } else { + mResponse.cancel(true); + } + } + } + + /** + * A data class wrapping IME responses for the inline suggestion request. + */ + public static class ImeResponse { + @NonNull + private final InlineSuggestionsRequest mRequest; + + @NonNull + private final IInlineSuggestionsResponseCallback mCallback; + + ImeResponse(@NonNull InlineSuggestionsRequest request, + @NonNull IInlineSuggestionsResponseCallback callback) { + mRequest = request; + mCallback = callback; + } + + public InlineSuggestionsRequest getRequest() { + return mRequest; + } + + public IInlineSuggestionsResponseCallback getCallback() { + return mCallback; + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index a25d7353edcb..53f85ea7b119 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -100,7 +100,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.ArrayUtils; -import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInlineSuggestionsResponseCallback; import com.android.server.autofill.ui.AutoFillUI; import com.android.server.autofill.ui.InlineSuggestionFactory; @@ -114,11 +113,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; /** @@ -159,9 +153,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** uid the session is for */ public final int uid; - /** user id the session is for */ - public final int userId; - /** ID of the task associated with this session's activity */ public final int taskId; @@ -310,12 +301,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private boolean mForAugmentedAutofillOnly; - @NonNull - private final InputMethodManagerInternal mInputMethodManagerInternal; - @Nullable - @GuardedBy("mLock") - private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback; + private final InlineSuggestionSession mInlineSuggestionSession; /** * Receiver of assist data from the app's {@link Activity}. @@ -416,12 +403,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FillContext> contexts = mergePreviousSessionLocked(/* forSave= */ false); - final InlineSuggestionsRequest suggestionsRequest = - mInlineSuggestionsRequestCallback != null - ? mInlineSuggestionsRequestCallback.getRequest() : null; + final InlineSuggestionSession.ImeResponse imeResponse = + mInlineSuggestionSession.waitAndGetImeResponse(); request = new FillRequest(requestId, contexts, mClientState, flags, - suggestionsRequest); + imeResponse != null ? imeResponse.getRequest() : null); } if (mActivityToken != null) { @@ -619,75 +605,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState, int newState, int flags) { if (isInlineSuggestionsEnabled()) { - mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(); - mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(userId, - mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback); + mInlineSuggestionSession.createRequest(mCurrentViewId); } requestNewFillResponseLocked(viewState, newState, flags); } - private static final class InlineSuggestionsRequestCallbackImpl - extends IInlineSuggestionsRequestCallback.Stub { - private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; - - private final CompletableFuture<InlineSuggestionsRequest> mRequest; - private final CompletableFuture<IInlineSuggestionsResponseCallback> mResponseCallback; - - private InlineSuggestionsRequestCallbackImpl() { - mRequest = new CompletableFuture<>(); - mResponseCallback = new CompletableFuture<>(); - } - - @Override - public void onInlineSuggestionsUnsupported() throws RemoteException { - if (sDebug) { - Log.d(TAG, "inline suggestions request unsupported, " - + "falling back to regular autofill"); - } - mRequest.cancel(true); - mResponseCallback.cancel(true); - } - - @Override - public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, - IInlineSuggestionsResponseCallback callback) throws RemoteException { - if (sDebug) { - Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); - } - mRequest.complete(request); - mResponseCallback.complete(callback); - } - - @Nullable - private InlineSuggestionsRequest getRequest() { - try { - return mRequest.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions request in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions request cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - return null; - } - - @Nullable - private IInlineSuggestionsResponseCallback getResponseCallback() { - try { - return mResponseCallback.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions callback cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - return null; - } - } - /** * Reads a new structure and then request a new fill response from the fill service. */ @@ -767,7 +690,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFlags = flags; this.taskId = taskId; this.uid = uid; - this.userId = userId; mStartTime = SystemClock.elapsedRealtime(); mService = service; mLock = lock; @@ -786,7 +708,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mForAugmentedAutofillOnly = forAugmentedAutofillOnly; setClientLocked(client); - mInputMethodManagerInternal = inputMethodManagerInternal; + mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId, + componentName); mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); @@ -2679,7 +2602,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (response.supportsInlineSuggestions()) { synchronized (mLock) { - if (requestShowInlineSuggestions(response, mInlineSuggestionsRequestCallback)) { + if (requestShowInlineSuggestionsLocked(response)) { //TODO(b/137800469): Fix it to log showed only when IME asks for inflation, // rather than here where framework sends back the response. mService.logDatasetShown(id, mClientState); @@ -2722,21 +2645,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Returns whether we made a request to show inline suggestions. */ - private boolean requestShowInlineSuggestions(@NonNull FillResponse response, - @Nullable InlineSuggestionsRequestCallbackImpl callback) { + private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response) { final List<Dataset> datasets = response.getDatasets(); if (datasets == null) { Log.w(TAG, "response returned null datasets"); return false; } - if (callback == null || callback.getRequest() == null - || callback.getResponseCallback() == null) { + final InlineSuggestionSession.ImeResponse imeResponse = + mInlineSuggestionSession.waitAndGetImeResponse(); + if (imeResponse == null) { Log.w(TAG, "Session input method callback is not set yet"); return false; } - final InlineSuggestionsRequest request = callback.getRequest(); + final InlineSuggestionsRequest request = imeResponse.getRequest(); InlineSuggestionsResponse inlineSuggestionsResponse = InlineSuggestionFactory.createInlineSuggestionsResponse(request, response.getRequestId(), @@ -2747,7 +2670,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } }); try { - callback.getResponseCallback().onInlineSuggestionsResponse(inlineSuggestionsResponse); + imeResponse.getCallback().onInlineSuggestionsResponse(inlineSuggestionsResponse); } catch (RemoteException e) { Log.w(TAG, "onFillReady() remote error calling onInlineSuggestionsResponse()"); return false; @@ -3029,12 +2952,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId); + // There are 3 cases when augmented autofill should ask IME for a new request: + // 1. standard autofill provider is None + // 2. standard autofill provider doesn't support inline (and returns null response) + // 3. standard autofill provider supports inline, but isn't called because the field + // doesn't want autofill + if (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabled()) { + if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); + mInlineSuggestionSession.createRequest(mCurrentViewId); + } + InlineSuggestionSession.ImeResponse imeResponse = + mInlineSuggestionSession.waitAndGetImeResponse(); final InlineSuggestionsRequest inlineSuggestionsRequest = - mInlineSuggestionsRequestCallback != null - ? mInlineSuggestionsRequestCallback.getRequest() : null; + imeResponse != null ? imeResponse.getRequest() : null; final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback = - mInlineSuggestionsRequestCallback != null - ? mInlineSuggestionsRequestCallback.getResponseCallback() : null; + imeResponse != null ? imeResponse.getCallback() : null; remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId, currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback, () -> { synchronized (mLock) { diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java index 8d476d72c639..e813dae76d92 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java @@ -48,6 +48,7 @@ class InlineSuggestionRoot extends FrameLayout { super(context); mOnErrorCallback = onErrorCallback; mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + setFocusable(false); } @Override diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index e3d2dcc8141f..6247a635233a 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -1080,12 +1080,8 @@ public class UserBackupManagerService { } } - public Map<String, Set<String>> getExcludedRestoreKeys(String... packages) { - return mBackupPreferences.getExcludedRestoreKeysForPackages(packages); - } - - public Map<String, Set<String>> getAllExcludedRestoreKeys() { - return mBackupPreferences.getAllExcludedRestoreKeys(); + public Set<String> getExcludedRestoreKeys(String packageName) { + return mBackupPreferences.getExcludedRestoreKeysForPackage(packageName); } /** Used for generating random salts or passwords. */ @@ -3356,8 +3352,7 @@ public class UserBackupManagerService { restoreSet, packageName, token, - listener, - getExcludedRestoreKeys(packageName)); + listener); mBackupHandler.sendMessage(msg); } catch (Exception e) { // Calling into the transport broke; back off and proceed with the installation. diff --git a/services/backup/java/com/android/server/backup/UserBackupPreferences.java b/services/backup/java/com/android/server/backup/UserBackupPreferences.java index 41b9719d273b..bb8bf52187c5 100644 --- a/services/backup/java/com/android/server/backup/UserBackupPreferences.java +++ b/services/backup/java/com/android/server/backup/UserBackupPreferences.java @@ -48,16 +48,7 @@ public class UserBackupPreferences { mEditor.commit(); } - Map<String, Set<String>> getExcludedRestoreKeysForPackages(String... packages) { - Map<String, Set<String>> excludedKeys = new HashMap<>(); - for (String packageName : packages) { - excludedKeys.put(packageName, - mPreferences.getStringSet(packageName, Collections.emptySet())); - } - return excludedKeys; - } - - Map<String, Set<String>> getAllExcludedRestoreKeys() { - return (Map<String, Set<String>>) mPreferences.getAll(); + Set<String> getExcludedRestoreKeysForPackage(String packageName) { + return mPreferences.getStringSet(packageName, Collections.emptySet()); } } diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index 05396f36b364..87a8e4982529 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -299,8 +299,7 @@ public class BackupHandler extends Handler { params.pmToken, params.isSystemRestore, params.filterSet, - params.listener, - params.excludedKeys); + params.listener); synchronized (backupManagerService.getPendingRestores()) { if (backupManagerService.isRestoreInProgress()) { diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java index 09b7e3535e2e..a6fea6cc75a0 100644 --- a/services/backup/java/com/android/server/backup/params/RestoreParams.java +++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java @@ -37,7 +37,6 @@ public class RestoreParams { public final boolean isSystemRestore; @Nullable public final String[] filterSet; public final OnTaskFinishedListener listener; - public final Map<String, Set<String>> excludedKeys; /** * No kill after restore. @@ -48,8 +47,7 @@ public class RestoreParams { IBackupManagerMonitor monitor, long token, PackageInfo packageInfo, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { return new RestoreParams( transportClient, observer, @@ -59,8 +57,7 @@ public class RestoreParams { /* pmToken */ 0, /* isSystemRestore */ false, /* filterSet */ null, - listener, - excludedKeys); + listener); } /** @@ -73,8 +70,7 @@ public class RestoreParams { long token, String packageName, int pmToken, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { String[] filterSet = {packageName}; return new RestoreParams( transportClient, @@ -85,8 +81,7 @@ public class RestoreParams { pmToken, /* isSystemRestore */ false, filterSet, - listener, - excludedKeys); + listener); } /** @@ -97,8 +92,7 @@ public class RestoreParams { IRestoreObserver observer, IBackupManagerMonitor monitor, long token, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { return new RestoreParams( transportClient, observer, @@ -108,8 +102,7 @@ public class RestoreParams { /* pmToken */ 0, /* isSystemRestore */ true, /* filterSet */ null, - listener, - excludedKeys); + listener); } /** @@ -122,8 +115,7 @@ public class RestoreParams { long token, String[] filterSet, boolean isSystemRestore, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { return new RestoreParams( transportClient, observer, @@ -133,8 +125,7 @@ public class RestoreParams { /* pmToken */ 0, isSystemRestore, filterSet, - listener, - excludedKeys); + listener); } private RestoreParams( @@ -146,8 +137,7 @@ public class RestoreParams { int pmToken, boolean isSystemRestore, @Nullable String[] filterSet, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { this.transportClient = transportClient; this.observer = observer; this.monitor = monitor; @@ -157,6 +147,5 @@ public class RestoreParams { this.isSystemRestore = isSystemRestore; this.filterSet = filterSet; this.listener = listener; - this.excludedKeys = excludedKeys; } } diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java index c0f76c39e04f..5a57cdc39402 100644 --- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java +++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java @@ -178,8 +178,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { observer, monitor, token, - listener, - mBackupManagerService.getAllExcludedRestoreKeys()), + listener), "RestoreSession.restoreAll()"); } finally { Binder.restoreCallingIdentity(oldId); @@ -272,8 +271,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { token, packages, /* isSystemRestore */ packages.length > 1, - listener, - mBackupManagerService.getExcludedRestoreKeys(packages)), + listener), "RestoreSession.restorePackages(" + packages.length + " packages)"); } finally { Binder.restoreCallingIdentity(oldId); @@ -365,8 +363,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { monitor, token, app, - listener, - mBackupManagerService.getExcludedRestoreKeys(app.packageName)), + listener), "RestoreSession.restorePackage(" + packageName + ")"); } finally { Binder.restoreCallingIdentity(oldId); 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 f90d936a5f4d..3c37f737f8be 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -155,8 +155,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // When finished call listener private final OnTaskFinishedListener mListener; - private final Map<String, Set<String>> mExcludedKeys; - // Key/value: bookkeeping about staged data and files for agent access private File mBackupDataName; private File mStageName; @@ -168,14 +166,14 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { private final BackupAgentTimeoutParameters mAgentTimeoutParameters; @VisibleForTesting - PerformUnifiedRestoreTask(Map<String, Set<String>> excludedKeys) { - mExcludedKeys = excludedKeys; + PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) { mListener = null; mAgentTimeoutParameters = null; mTransportClient = null; mTransportManager = null; mEphemeralOpToken = 0; mUserId = 0; + this.backupManagerService = backupManagerService; } // This task can assume that the wakelock is properly held for it and doesn't have to worry @@ -190,8 +188,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { int pmToken, boolean isFullSystemRestore, @Nullable String[] filterSet, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { this.backupManagerService = backupManagerService; mUserId = backupManagerService.getUserId(); mTransportManager = backupManagerService.getTransportManager(); @@ -213,8 +210,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { backupManagerService.getAgentTimeoutParameters(), "Timeout parameters cannot be null"); - mExcludedKeys = excludedKeys; - if (targetPackage != null) { // Single package restore mAcceptSet = new ArrayList<>(); @@ -791,8 +786,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { !getExcludedKeysForPackage(PLATFORM_PACKAGE_NAME).isEmpty(); } - private Set<String> getExcludedKeysForPackage(String packageName) { - return mExcludedKeys.getOrDefault(packageName, Collections.emptySet()); + @VisibleForTesting + Set<String> getExcludedKeysForPackage(String packageName) { + return backupManagerService.getExcludedRestoreKeys(packageName); } @VisibleForTesting diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index caacf13d7bc4..687fb7d7200d 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3123,7 +3123,13 @@ public class ConnectivityService extends IConnectivityManager.Stub handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties)); } - private void updateLingerState(NetworkAgentInfo nai, long now) { + /** + * Updates the linger state from the network requests inside the NAI. + * @param nai the agent info to update + * @param now the timestamp of the event causing this update + * @return whether the network was lingered as a result of this update + */ + private boolean updateLingerState(@NonNull final NetworkAgentInfo nai, final long now) { // 1. Update the linger timer. If it's changed, reschedule or cancel the alarm. // 2. If the network was lingering and there are now requests, unlinger it. // 3. If this network is unneeded (which implies it is not lingering), and there is at least @@ -3134,12 +3140,15 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.unlinger(); logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER); } else if (unneeded(nai, UnneededFor.LINGER) && nai.getLingerExpiry() > 0) { - int lingerTime = (int) (nai.getLingerExpiry() - now); - if (DBG) log("Lingering " + nai.name() + " for " + lingerTime + "ms"); + if (DBG) { + final int lingerTime = (int) (nai.getLingerExpiry() - now); + log("Lingering " + nai.name() + " for " + lingerTime + "ms"); + } nai.linger(); logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER); - notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime); + return true; } + return false; } private void handleAsyncChannelHalfConnect(Message msg) { @@ -3483,7 +3492,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } // If there are still lingered requests on this network, don't tear it down, // but resume lingering instead. - updateLingerState(nai, SystemClock.elapsedRealtime()); + final long now = SystemClock.elapsedRealtime(); + if (updateLingerState(nai, now)) { + notifyNetworkLosing(nai, now); + } if (unneeded(nai, UnneededFor.TEARDOWN)) { if (DBG) log("no live requests for " + nai.name() + "; disconnecting"); teardownUnneededNetwork(nai); @@ -6620,11 +6632,16 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (currentNetwork == null || currentNetwork.getCurrentScore() < score) { reassignedRequests.put(nri, newNetwork); + changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( + nri, currentNetwork, newNetwork)); } } else if (newNetwork == currentNetwork) { reassignedRequests.put(nri, null); + changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( + nri, currentNetwork, null)); } } + return reassignedRequests; } @@ -6663,39 +6680,37 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkRequestInfo nri = entry.getKey(); final NetworkAgentInfo previousSatisfier = nri.mSatisfier; final NetworkAgentInfo newSatisfier = entry.getValue(); - changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( - nri, previousSatisfier, newSatisfier)); - if (newSatisfier != null) { - if (VDBG) log("rematch for " + newSatisfier.name()); - if (previousSatisfier != null) { - if (VDBG || DDBG) { - log(" accepting network in place of " + previousSatisfier.name()); - } - previousSatisfier.removeRequest(nri.request.requestId); - previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs); - } else { - if (VDBG || DDBG) log(" accepting network in place of null"); - } - newSatisfier.unlingerRequest(nri.request); - if (!newSatisfier.addRequest(nri.request)) { - Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request); + updateSatisfiersForRematchRequest(nri, previousSatisfier, newSatisfier, now); + } + } + + private void updateSatisfiersForRematchRequest(@NonNull final NetworkRequestInfo nri, + @Nullable final NetworkAgentInfo previousSatisfier, + @Nullable final NetworkAgentInfo newSatisfier, + final long now) { + if (newSatisfier != null) { + if (VDBG) log("rematch for " + newSatisfier.name()); + if (previousSatisfier != null) { + if (VDBG || DDBG) { + log(" accepting network in place of " + previousSatisfier.name()); } + previousSatisfier.removeRequest(nri.request.requestId); + previousSatisfier.lingerRequest(nri.request, now, mLingerDelayMs); } else { - // If "newNetwork" is listed as satisfying "nri" but no longer satisfies "nri", - // mark it as no longer satisfying "nri". Because networks are processed by - // rematchAllNetworksAndRequests() in descending score order, "currentNetwork" will - // match "newNetwork" before this loop will encounter a "currentNetwork" with higher - // score than "newNetwork" and where "currentNetwork" no longer satisfies "nri". - // This means this code doesn't have to handle the case where "currentNetwork" no - // longer satisfies "nri" when "currentNetwork" does not equal "newNetwork". - if (DBG) { - log("Network " + newNetwork.name() + " stopped satisfying" + - " request " + nri.request.requestId); - } - newNetwork.removeRequest(nri.request.requestId); + if (VDBG || DDBG) log(" accepting network in place of null"); + } + newSatisfier.unlingerRequest(nri.request); + if (!newSatisfier.addRequest(nri.request)) { + Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request); + } + } else { + if (DBG) { + log("Network " + previousSatisfier.name() + " stopped satisfying" + + " request " + nri.request.requestId); } - nri.mSatisfier = newSatisfier; + previousSatisfier.removeRequest(nri.request.requestId); } + nri.mSatisfier = newSatisfier; } /** @@ -6773,13 +6788,20 @@ public class ConnectivityService extends IConnectivityManager.Stub processNewlySatisfiedListenRequests(event.mNetwork); } + final ArrayList<NetworkAgentInfo> lingeredNetworks = new ArrayList<>(); for (final NetworkAgentInfo nai : nais) { // Rematching may have altered the linger state of some networks, so update all linger // timers. updateLingerState reads the state from the network agent and does nothing // if the state has not changed : the source of truth is controlled with // NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been // called while rematching the individual networks above. - updateLingerState(nai, now); + if (updateLingerState(nai, now)) { + lingeredNetworks.add(nai); + } + } + + for (final NetworkAgentInfo nai : lingeredNetworks) { + notifyNetworkLosing(nai, now); } updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais); @@ -6795,7 +6817,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // and became unneeded due to another network improving its score to the // point where this network will no longer be able to satisfy any requests // even if it validates. - updateLingerState(nai, now); + if (updateLingerState(nai, now)) { + notifyNetworkLosing(nai, now); + } } else { if (DBG) log("Reaping " + nai.name()); teardownUnneededNetwork(nai); @@ -7048,6 +7072,12 @@ public class ConnectivityService extends IConnectivityManager.Stub callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0); } + // Notify the requests on this NAI that the network is now lingered. + private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) { + final int lingerTime = (int) (nai.getLingerExpiry() - now); + notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime); + } + /** * Notify of the blocked state apps with a registered callback matching a given NAI. * diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index 135f6f3ea125..6fb7b267c285 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -272,10 +272,11 @@ public final class PinnerService extends SystemService { private void handlePinOnStart() { final String bootImage = SystemProperties.get("dalvik.vm.boot-image", ""); String[] filesToPin = null; - if (bootImage.endsWith("apex.art")) { - // Use the files listed for that specific boot image + if (bootImage.endsWith("boot-image.prof")) { + // Use the files listed for that specific boot image. + // TODO: find a better way to know we're using the JIT zygote configuration. filesToPin = mContext.getResources().getStringArray( - com.android.internal.R.array.config_apexBootImagePinnerServiceFiles); + com.android.internal.R.array.config_jitzygoteBootImagePinnerServiceFiles); } else { // Files to pin come from the overlay and can be specified per-device config filesToPin = mContext.getResources().getStringArray( diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index cbf6c274e865..0e5a6bb8bd1c 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -284,16 +284,16 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { static final int ENFORCE_PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR - | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST - | PhoneStateListener.LISTEN_REGISTRATION_FAILURE - | PhoneStateListener.LISTEN_BARRING_INFO; + | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST; - static final int PRECISE_PHONE_STATE_PERMISSION_MASK = + static final int ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK = PhoneStateListener.LISTEN_PRECISE_CALL_STATE - | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE - | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES - | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED - | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES; + | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE + | PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES + | PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED + | PhoneStateListener.LISTEN_IMS_CALL_DISCONNECT_CAUSES + | PhoneStateListener.LISTEN_REGISTRATION_FAILURE + | PhoneStateListener.LISTEN_BARRING_INFO; static final int READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK = PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL @@ -2535,7 +2535,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - if ((events & PRECISE_PHONE_STATE_PERMISSION_MASK) != 0) { + if ((events & ENFORCE_PRECISE_PHONE_STATE_PERMISSION_MASK) != 0) { // check if calling app has either permission READ_PRECISE_PHONE_STATE // or with carrier privileges try { diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index b994e6c58e64..f2d5a9b685f4 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -302,15 +302,19 @@ final class UiModeManagerService extends SystemService { private final ContentObserver mDarkThemeObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Uri uri) { - int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE, - mNightMode, 0); - if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) { - mode = MODE_NIGHT_YES; - } - SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode)); + updateSystemProperties(); } }; + private void updateSystemProperties() { + int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE, + mNightMode, 0); + if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) { + mode = MODE_NIGHT_YES; + } + SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode)); + } + @Override public void onSwitchUser(int userHandle) { super.onSwitchUser(userHandle); @@ -392,6 +396,7 @@ final class UiModeManagerService extends SystemService { context.getContentResolver().registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE), false, mDarkThemeObserver, 0); + updateSystemProperties(); } @VisibleForTesting diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3ad96ea193bf..0686e3e99b2f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -466,18 +466,9 @@ public class ActivityManagerService extends IActivityManager.Stub // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real. static final int PROC_START_TIMEOUT = 10*1000; - // How long we wait for an attached process to publish its content providers - // before we decide it must be hung. - static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000; - // How long we wait to kill an application zygote, after the last process using // it has gone away. static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000; - /** - * How long we wait for an provider to be published. Should be longer than - * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT}. - */ - static final int CONTENT_PROVIDER_WAIT_TIMEOUT = 20 * 1000; // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real, when the process was @@ -4934,7 +4925,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (providers != null && checkAppInLaunchingProvidersLocked(app)) { Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG); msg.obj = app; - mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT); + mHandler.sendMessageDelayed(msg, + ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS); } checkTime(startTime, "attachApplicationLocked: before bindApplication"); @@ -7201,7 +7193,8 @@ public class ActivityManagerService extends IActivityManager.Stub } // Wait for the provider to be published... - final long timeout = SystemClock.uptimeMillis() + CONTENT_PROVIDER_WAIT_TIMEOUT; + final long timeout = + SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS; boolean timedOut = false; synchronized (cpr) { while (cpr.provider == null) { @@ -7238,12 +7231,14 @@ public class ActivityManagerService extends IActivityManager.Stub } } if (timedOut) { - // Note we do it afer releasing the lock. + // Note we do it after releasing the lock. String callerName = "unknown"; - synchronized (this) { - final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller); - if (record != null) { - callerName = record.processName; + if (caller != null) { + synchronized (this) { + final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller); + if (record != null) { + callerName = record.processName; + } } } @@ -11071,10 +11066,12 @@ public class ActivityManagerService extends IActivityManager.Stub } if (dumpAll || dumpPackage != null) { + final SparseArray<ProcessRecord> pidToProcess = new SparseArray<>(); synchronized (mPidsSelfLocked) { boolean printed = false; for (int i=0; i<mPidsSelfLocked.size(); i++) { ProcessRecord r = mPidsSelfLocked.valueAt(i); + pidToProcess.put(r.pid, r); if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) { continue; } @@ -11088,6 +11085,32 @@ public class ActivityManagerService extends IActivityManager.Stub pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i)); } } + + synchronized (sActiveProcessInfoSelfLocked) { + boolean printed = false; + for (int i = 0; i < sActiveProcessInfoSelfLocked.size(); i++) { + ProcessInfo info = sActiveProcessInfoSelfLocked.valueAt(i); + ProcessRecord r = pidToProcess.get(sActiveProcessInfoSelfLocked.keyAt(i)); + if (r != null && dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) { + continue; + } + if (!printed) { + if (needSep) pw.println(); + needSep = true; + pw.println(" Active process infos:"); + printed = true; + } + pw.print(" Pinfo PID #"); pw.print(sActiveProcessInfoSelfLocked.keyAt(i)); + pw.println(":"); + pw.print(" name="); pw.println(info.name); + if (info.deniedPermissions != null) { + for (int j = 0; j < info.deniedPermissions.size(); j++) { + pw.print(" deny: "); + pw.println(info.deniedPermissions.valueAt(i)); + } + } + } + } } if (mImportantProcesses.size() > 0) { diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index d047a3ca993c..eec68dc7353e 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -946,6 +946,15 @@ public final class CachedAppOptimizer { } EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name); + + // See above for why we're not taking mPhenotypeFlagLock here + if (mRandom.nextFloat() < mFreezerStatsdSampleRate) { + FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED, + FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP, + pid, + name, + unfrozenDuration); + } } } @@ -994,6 +1003,16 @@ public final class CachedAppOptimizer { } EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, name); + + // See above for why we're not taking mPhenotypeFlagLock here + if (mRandom.nextFloat() < mFreezerStatsdSampleRate) { + FrameworkStatsLog.write( + FrameworkStatsLog.APP_FREEZE_CHANGED, + FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__UNFREEZE_APP, + pid, + name, + frozenDuration); + } } } } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 2b4d15eda9f0..f3d8bc8cb64f 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -371,6 +371,15 @@ class ProcessRecord implements WindowProcessListener { } } pw.println("}"); + if (processInfo != null) { + pw.print(prefix); pw.println("processInfo:"); + if (processInfo.deniedPermissions != null) { + for (int i = 0; i < processInfo.deniedPermissions.size(); i++) { + pw.print(prefix); pw.print(" deny: "); + pw.println(processInfo.deniedPermissions.valueAt(i)); + } + } + } pw.print(prefix); pw.print("mRequiredAbi="); pw.print(mRequiredAbi); pw.print(" instructionSet="); pw.println(instructionSet); if (info.className != null) { diff --git a/services/core/java/com/android/server/compat/TEST_MAPPING b/services/core/java/com/android/server/compat/TEST_MAPPING new file mode 100644 index 000000000000..0c30c790c5dd --- /dev/null +++ b/services/core/java/com/android/server/compat/TEST_MAPPING @@ -0,0 +1,21 @@ +{ + "presubmit": [ + // Unit tests + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.compat" + } + ] + }, + // Tests for the TestRule + { + "name": "PlatformCompatGating" + }, + // CTS tests + { + "name": "CtsAppCompatHostTestCases#" + } + ] +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java index b0e2e6432c77..6a5e963064bb 100644 --- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java +++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java @@ -16,9 +16,7 @@ package com.android.server.incremental; -import static android.content.pm.InstallationFile.FILE_TYPE_OBB; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; -import static android.content.pm.PackageInstaller.LOCATION_MEDIA_OBB; import android.annotation.IntDef; import android.annotation.NonNull; @@ -183,10 +181,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { session = packageInstaller.openSession(sessionId); for (int i = 0; i < numFiles; i++) { InstallationFile file = installationFiles.get(i); - final int location = file.getFileType() == FILE_TYPE_OBB ? LOCATION_MEDIA_OBB - : LOCATION_DATA_APP; - session.addFile(location, file.getName(), file.getSize(), file.getMetadata(), - null); + session.addFile(file.getLocation(), file.getName(), file.getLengthBytes(), + file.getMetadata(), file.getSignature()); } session.commit(localReceiver.getIntentSender()); final Intent result = localReceiver.getResult(); @@ -304,7 +300,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { } final byte[] metadata = String.valueOf(index).getBytes( StandardCharsets.UTF_8); - fileList.add(new InstallationFile(name, size, metadata)); + fileList.add( + new InstallationFile(LOCATION_DATA_APP, name, size, metadata, null)); break; } default: diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 0450647b9403..68ced79b3e38 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -272,21 +272,15 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { List<String> installerCertificates = getInstallerCertificateFingerprint(installerPackageName); - // TODO (b/148373316): Figure out what field contains which fields are populated for - // rotated and the multiple signers. Until then, return the first certificate. - String appCert = appCertificates.isEmpty() ? "" : appCertificates.get(0); - String installerCert = - installerCertificates.isEmpty() ? "" : installerCertificates.get(0); - Slog.w(TAG, appCertificates.toString()); AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder(); builder.setPackageName(getPackageNameNormalized(packageName)); - builder.setAppCertificate(appCert); + builder.setAppCertificates(appCertificates); builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1)); builder.setInstallerName(getPackageNameNormalized(installerPackageName)); - builder.setInstallerCertificate(installerCert); + builder.setInstallerCertificates(installerCertificates); builder.setIsPreInstalled(isSystemApp(packageName)); AppInstallMetadata appInstallMetadata = builder.build(); @@ -307,7 +301,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { FrameworkStatsLog.write( FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED, packageName, - appCert, + appCertificates.toString(), appInstallMetadata.getVersionCode(), installerPackageName, result.getLoggingResponse(), diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java index 87eee4ea7be4..60e8cce55730 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java @@ -63,10 +63,12 @@ public class RuleIndexingController { searchIndexingKeysRangeContainingKey( sPackageNameBasedIndexes, appInstallMetadata.getPackageName())); - // Add the range for app certificate indexes rules. - indexRanges.add( - searchIndexingKeysRangeContainingKey( - sAppCertificateBasedIndexes, appInstallMetadata.getAppCertificate())); + // Add the range for app certificate indexes rules of all certificates. + for (String appCertificate : appInstallMetadata.getAppCertificates()) { + indexRanges.add( + searchIndexingKeysRangeContainingKey( + sAppCertificateBasedIndexes, appCertificate)); + } // Add the range for unindexed rules. indexRanges.add( diff --git a/services/core/java/com/android/server/location/TEST_MAPPING b/services/core/java/com/android/server/location/TEST_MAPPING index 2e21fa67a967..214d2f3f5646 100644 --- a/services/core/java/com/android/server/location/TEST_MAPPING +++ b/services/core/java/com/android/server/location/TEST_MAPPING @@ -1,10 +1,19 @@ { "presubmit": [ { + "name": "CtsLocationFineTestCases" + }, + { "name": "CtsLocationCoarseTestCases" }, { "name": "CtsLocationNoneTestCases" + }, + { + "name": "FrameworksMockingServicesTests", + "options": [{ + "include-filter": "com.android.server.location" + }] } ] }
\ No newline at end of file diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java new file mode 100644 index 000000000000..58c2707a1f19 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -0,0 +1,360 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Handler; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + +/** + * Holds the media button receiver, and also provides helper methods around it. + */ +final class MediaButtonReceiverHolder { + public static final int COMPONENT_TYPE_INVALID = 0; + public static final int COMPONENT_TYPE_BROADCAST = 1; + public static final int COMPONENT_TYPE_ACTIVITY = 2; + public static final int COMPONENT_TYPE_SERVICE = 3; + + @IntDef(value = { + COMPONENT_TYPE_INVALID, + COMPONENT_TYPE_BROADCAST, + COMPONENT_TYPE_ACTIVITY, + COMPONENT_TYPE_SERVICE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ComponentType {} + + private static final String TAG = "PendingIntentHolder"; + private static final boolean DEBUG_KEY_EVENT = MediaSessionService.DEBUG_KEY_EVENT; + private static final String COMPONENT_NAME_USER_ID_DELIM = ","; + + private final int mUserId; + private final PendingIntent mPendingIntent; + private final ComponentName mComponentName; + private final String mPackageName; + @ComponentType + private final int mComponentType; + + /** + * Unflatten from string which is previously flattened string via flattenToString(). + * <p> + * It's used to store and restore media button receiver across the boot, by keeping the intent's + * component name to the persistent storage. + * + * @param mediaButtonReceiverInfo previously flattened string via flattenToString() + * @return new instance if the string was valid. {@code null} otherwise. + */ + public static MediaButtonReceiverHolder unflattenFromString( + Context context, String mediaButtonReceiverInfo) { + if (TextUtils.isEmpty(mediaButtonReceiverInfo)) { + return null; + } + String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM); + if (tokens == null || (tokens.length != 2 && tokens.length != 3)) { + return null; + } + ComponentName componentName = ComponentName.unflattenFromString(tokens[0]); + int userId = Integer.parseInt(tokens[1]); + // Guess component type if the OS version is updated from the older version. + int componentType = (tokens.length == 3) + ? Integer.parseInt(tokens[2]) + : getComponentType(context, componentName); + return new MediaButtonReceiverHolder(userId, null, componentName, componentType); + } + + /** + * Creates a new instance. + * + * @param context context + * @param userId userId + * @param pendingIntent pending intent + * @return Can be {@code null} if pending intent was null. + */ + public static MediaButtonReceiverHolder create(Context context, int userId, + PendingIntent pendingIntent) { + if (pendingIntent == null) { + return null; + } + ComponentName componentName = (pendingIntent != null && pendingIntent.getIntent() != null) + ? pendingIntent.getIntent().getComponent() : null; + if (componentName != null) { + // Explicit intent, where component name is in the PendingIntent. + return new MediaButtonReceiverHolder(userId, pendingIntent, componentName, + getComponentType(context, componentName)); + } + + // Implicit intent, where component name isn't in the PendingIntent. Try resolve. + PackageManager pm = context.getPackageManager(); + Intent intent = pendingIntent.getIntent(); + if ((componentName = resolveImplicitServiceIntent(pm, intent)) != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_SERVICE); + } else if ((componentName = resolveManifestDeclaredBroadcastReceiverIntent(pm, intent)) + != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_BROADCAST); + } else if ((componentName = resolveImplicitActivityIntent(pm, intent)) != null) { + return new MediaButtonReceiverHolder( + userId, pendingIntent, componentName, COMPONENT_TYPE_ACTIVITY); + } + + // Failed to resolve target component for the pending intent. It's unlikely to be usable. + // However, the pending intent would be still used, just to follow the legacy behavior. + Log.w(TAG, "Unresolvable implicit intent is set, pi=" + pendingIntent); + String packageName = (pendingIntent != null && pendingIntent.getIntent() != null) + ? pendingIntent.getIntent().getPackage() : null; + return new MediaButtonReceiverHolder(userId, pendingIntent, + packageName != null ? packageName : ""); + } + + private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, + ComponentName componentName, @ComponentType int componentType) { + mUserId = userId; + mPendingIntent = pendingIntent; + mComponentName = componentName; + mPackageName = componentName.getPackageName(); + mComponentType = componentType; + } + + private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, String packageName) { + mUserId = userId; + mPendingIntent = pendingIntent; + mComponentName = null; + mPackageName = packageName; + mComponentType = COMPONENT_TYPE_INVALID; + } + + /** + * @return the user id + */ + public int getUserId() { + return mUserId; + } + + /** + * @return package name that the media button receiver would be sent to. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Sends the media key event to the media button receiver. + * <p> + * This prioritizes using use pending intent for sending media key event. + * + * @param context context to be used to call PendingIntent#send + * @param keyEvent keyEvent to send + * @param resultCode result code to be used to call PendingIntent#send + * Ignored if there's no valid pending intent. + * @param onFinishedListener callback to be used to get result of PendingIntent#send. + * Ignored if there's no valid pending intent. + * @param handler handler to be used to call onFinishedListener + * Ignored if there's no valid pending intent. + * @see PendingIntent#send(Context, int, Intent, PendingIntent.OnFinished, Handler) + */ + public boolean send(Context context, KeyEvent keyEvent, String callingPackageName, + int resultCode, PendingIntent.OnFinished onFinishedListener, Handler handler) { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); + // TODO: Find a way to also send PID/UID in secure way. + mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName); + + if (mPendingIntent != null) { + if (DEBUG_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + " to the last known PendingIntent " + + mPendingIntent); + } + try { + mPendingIntent.send( + context, resultCode, mediaButtonIntent, onFinishedListener, handler); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Error sending key event to media button receiver " + mPendingIntent, e); + return false; + } + } else if (mComponentName != null) { + if (DEBUG_KEY_EVENT) { + Log.d(TAG, "Sending " + keyEvent + " to the restored intent " + + mComponentName + ", type=" + mComponentType); + } + mediaButtonIntent.setComponent(mComponentName); + UserHandle userHandle = UserHandle.of(mUserId); + try { + switch (mComponentType) { + case COMPONENT_TYPE_ACTIVITY: + context.startActivityAsUser(mediaButtonIntent, userHandle); + break; + case COMPONENT_TYPE_SERVICE: + context.startForegroundServiceAsUser(mediaButtonIntent, + userHandle); + break; + default: + // Legacy behavior for other cases. + context.sendBroadcastAsUser(mediaButtonIntent, userHandle); + } + } catch (Exception e) { + Log.w(TAG, "Error sending media button to the restored intent " + + mComponentName + ", type=" + mComponentType, e); + return false; + } + } else { + // Leave log, just in case. + Log.e(TAG, "Shouldn't be happen -- pending intent or component name must be set"); + return false; + } + return true; + } + + + @Override + public String toString() { + if (mPendingIntent != null) { + return "MBR {pi=" + mPendingIntent + ", type=" + mComponentType + "}"; + } + return "Restored MBR {component=" + mComponentName + ", type=" + mComponentType + "}"; + } + + /** + * @return flattened string. Can be empty string if the MBR is created with implicit intent. + */ + public String flattenToString() { + if (mComponentName == null) { + // We don't know which component would receive the key event. + return ""; + } + return String.join(COMPONENT_NAME_USER_ID_DELIM, + mComponentName.toString(), + String.valueOf(mUserId), + String.valueOf(mComponentType)); + } + + /** + * Gets the type of the component + * + * @param context context + * @param componentName component name + * @return A component type + */ + @ComponentType + private static int getComponentType(Context context, ComponentName componentName) { + if (componentName == null) { + return COMPONENT_TYPE_INVALID; + } + PackageManager pm = context.getPackageManager(); + try { + ActivityInfo activityInfo = pm.getActivityInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_ACTIVITIES); + if (activityInfo != null) { + return COMPONENT_TYPE_ACTIVITY; + } + } catch (PackageManager.NameNotFoundException e) { + } + try { + ServiceInfo serviceInfo = pm.getServiceInfo(componentName, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_SERVICES); + if (serviceInfo != null) { + return COMPONENT_TYPE_SERVICE; + } + } catch (PackageManager.NameNotFoundException e) { + } + // Pick legacy behavior for BroadcastReceiver or unknown. + return COMPONENT_TYPE_BROADCAST; + } + + private static ComponentName resolveImplicitServiceIntent(PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // filter apps regardless of the phone's locked/unlocked state. + // - GET_SERVICES: Return service + return createComponentName(pm.resolveService(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_SERVICES)); + } + + private static ComponentName resolveManifestDeclaredBroadcastReceiverIntent( + PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // filter apps regardless of the phone's locked/unlocked state. + List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + return (resolveInfos != null && !resolveInfos.isEmpty()) + ? createComponentName(resolveInfos.get(0)) : null; + } + + private static ComponentName resolveImplicitActivityIntent(PackageManager pm, Intent intent) { + // Flag explanations. + // - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE: + // Filter apps regardless of the phone's locked/unlocked state. + // - MATCH_DEFAULT_ONLY: + // Implicit intent receiver should be set as default. Only needed for activity. + // - GET_ACTIVITIES: Return activity + return createComponentName(pm.resolveActivity(intent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DEFAULT_ONLY + | PackageManager.GET_ACTIVITIES)); + } + + private static ComponentName createComponentName(ResolveInfo resolveInfo) { + if (resolveInfo == null) { + return null; + } + ComponentInfo componentInfo; + // Code borrowed from ResolveInfo#getComponentInfo(). + if (resolveInfo.activityInfo != null) { + componentInfo = resolveInfo.activityInfo; + } else if (resolveInfo.serviceInfo != null) { + componentInfo = resolveInfo.serviceInfo; + } else { + // We're not interested in content provider. + return null; + } + // Code borrowed from ComponentInfo#getComponentName(). + try { + return new ComponentName(componentInfo.packageName, componentInfo.name); + } catch (IllegalArgumentException | NullPointerException e) { + // This may be happen if resolveActivity() end up with matching multiple activities. + // see PackageManager#resolveActivity(). + return null; + } + } +} diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index d08fb71d3dee..dd536ecd19fa 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -411,6 +411,12 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mActiveConnection.dispose(); mActiveConnection = null; setAndNotifyProviderState(null); + synchronized (mLock) { + for (RoutingSessionInfo sessionInfo : mSessionInfos) { + mCallback.onSessionReleased(this, sessionInfo); + } + mSessionInfos.clear(); + } } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 7bcbcd4a3d09..9f47b349d3fc 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -127,7 +127,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR new ArrayList<>(); private long mFlags; - private PendingIntent mMediaButtonReceiver; + private MediaButtonReceiverHolder mMediaButtonReceiverHolder; private PendingIntent mLaunchIntent; // TransportPerformer fields @@ -220,8 +220,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR * * @return The pending intent set by the app or null. */ - public PendingIntent getMediaButtonReceiver() { - return mMediaButtonReceiver; + public MediaButtonReceiverHolder getMediaButtonReceiver() { + return mMediaButtonReceiverHolder; } /** @@ -471,7 +471,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR + ", userId=" + mUserId); pw.println(indent + "package=" + mPackageName); pw.println(indent + "launchIntent=" + mLaunchIntent); - pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiver); + pw.println(indent + "mediaButtonReceiver=" + mMediaButtonReceiverHolder); pw.println(indent + "active=" + mIsActive); pw.println(indent + "flags=" + mFlags); pw.println(indent + "rating type=" + mRatingType); @@ -833,12 +833,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @Override public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException { - if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) == 1) { - return; - } - mMediaButtonReceiver = pi; final long token = Binder.clearCallingIdentity(); try { + if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) + != 0) { + return; + } + mMediaButtonReceiverHolder = + MediaButtonReceiverHolder.create(mContext, mUserId, pi); mService.onMediaButtonReceiverChanged(MediaSessionRecord.this); } finally { Binder.restoreCallingIdentity(token); @@ -1529,5 +1531,4 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR msg.sendToTarget(); } } - } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 88b884efce83..7ffac062b7f5 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -18,22 +18,17 @@ package com.android.server.media; import static android.os.UserHandle.USER_ALL; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.INotificationManager; import android.app.KeyguardManager; import android.app.PendingIntent; -import android.app.PendingIntent.CanceledException; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; -import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.media.AudioManager; @@ -99,7 +94,7 @@ public class MediaSessionService extends SystemService implements Monitor { private static final String TAG = "MediaSessionService"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); // Leave log for key event always. - private static final boolean DEBUG_KEY_EVENT = true; + static final boolean DEBUG_KEY_EVENT = true; private static final int WAKELOCK_TIMEOUT = 5000; private static final int MEDIA_KEY_LISTENER_TIMEOUT = 1000; @@ -760,12 +755,6 @@ public class MediaSessionService extends SystemService implements Monitor { * <p>The contents of this object is guarded by {@link #mLock}. */ final class FullUserRecord implements MediaSessionStack.OnMediaButtonSessionChangedListener { - public static final int COMPONENT_TYPE_INVALID = 0; - public static final int COMPONENT_TYPE_BROADCAST = 1; - public static final int COMPONENT_TYPE_ACTIVITY = 2; - public static final int COMPONENT_TYPE_SERVICE = 3; - private static final String COMPONENT_NAME_USER_ID_DELIM = ","; - private final int mFullUserId; private final MediaSessionStack mPriorityStack; private final HashMap<IBinder, OnMediaKeyEventDispatchedListenerRecord> @@ -774,10 +763,7 @@ public class MediaSessionService extends SystemService implements Monitor { mOnMediaKeyEventSessionChangedListeners = new HashMap<>(); private final SparseIntArray mUidToSessionCount = new SparseIntArray(); - private PendingIntent mLastMediaButtonReceiver; - private ComponentName mRestoredMediaButtonReceiver; - private int mRestoredMediaButtonReceiverComponentType; - private int mRestoredMediaButtonReceiverUserId; + private MediaButtonReceiverHolder mLastMediaButtonReceiverHolder; private IOnVolumeKeyLongPressListener mOnVolumeKeyLongPressListener; private int mOnVolumeKeyLongPressListenerUid; @@ -794,21 +780,9 @@ public class MediaSessionService extends SystemService implements Monitor { // Restore the remembered media button receiver before the boot. String mediaButtonReceiverInfo = Settings.Secure.getStringForUser(mContentResolver, Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId); - if (mediaButtonReceiverInfo == null) { - return; - } - String[] tokens = mediaButtonReceiverInfo.split(COMPONENT_NAME_USER_ID_DELIM); - if (tokens == null || (tokens.length != 2 && tokens.length != 3)) { - return; - } - mRestoredMediaButtonReceiver = ComponentName.unflattenFromString(tokens[0]); - mRestoredMediaButtonReceiverUserId = Integer.parseInt(tokens[1]); - if (tokens.length == 3) { - mRestoredMediaButtonReceiverComponentType = Integer.parseInt(tokens[2]); - } else { - mRestoredMediaButtonReceiverComponentType = - getComponentType(mRestoredMediaButtonReceiver); - } + mLastMediaButtonReceiverHolder = + MediaButtonReceiverHolder.unflattenFromString( + mContext, mediaButtonReceiverInfo); } public void destroySessionsForUserLocked(int userId) { @@ -892,10 +866,7 @@ public class MediaSessionService extends SystemService implements Monitor { : mOnMediaKeyEventSessionChangedListeners.values()) { pw.println(indent + " from " + getCallingPackageName(cr.uid)); } - pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiver); - pw.println(indent + "Restored MediaButtonReceiver: " + mRestoredMediaButtonReceiver); - pw.println(indent + "Restored MediaButtonReceiverComponentType: " - + mRestoredMediaButtonReceiverComponentType); + pw.println(indent + "Last MediaButtonReceiver: " + mLastMediaButtonReceiverHolder); mPriorityStack.dump(pw, indent); } @@ -924,25 +895,12 @@ public class MediaSessionService extends SystemService implements Monitor { return; } MediaSessionRecord sessionRecord = (MediaSessionRecord) record; - PendingIntent receiver = sessionRecord.getMediaButtonReceiver(); - mLastMediaButtonReceiver = receiver; - mRestoredMediaButtonReceiver = null; - mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID; - - String mediaButtonReceiverInfo = ""; - if (receiver != null) { - ComponentName component = receiver.getIntent().getComponent(); - if (component != null - && record.getPackageName().equals(component.getPackageName())) { - String componentName = component.flattenToString(); - int componentType = getComponentType(component); - mediaButtonReceiverInfo = String.join(COMPONENT_NAME_USER_ID_DELIM, - componentName, String.valueOf(record.getUserId()), - String.valueOf(componentType)); - } - } + mLastMediaButtonReceiverHolder = sessionRecord.getMediaButtonReceiver(); + String mediaButtonReceiverInfo = (mLastMediaButtonReceiverHolder == null) + ? "" : mLastMediaButtonReceiverHolder.flattenToString(); Settings.Secure.putStringForUser(mContentResolver, - Settings.System.MEDIA_BUTTON_RECEIVER, mediaButtonReceiverInfo, + Settings.System.MEDIA_BUTTON_RECEIVER, + mediaButtonReceiverInfo, mFullUserId); } @@ -958,15 +916,9 @@ public class MediaSessionService extends SystemService implements Monitor { } else { // TODO(jaewan): Implement } - } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) { - callback.onMediaKeyEventSessionChanged( - mCurrentFullUserRecord.mLastMediaButtonReceiver - .getIntent().getComponent().getPackageName(), - null); - } else if (mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) { - callback.onMediaKeyEventSessionChanged( - mCurrentFullUserRecord.mRestoredMediaButtonReceiver.getPackageName(), - null); + } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) { + String packageName = mLastMediaButtonReceiverHolder.getPackageName(); + callback.onMediaKeyEventSessionChanged(packageName, null); } } catch (RemoteException e) { Log.w(TAG, "Failed to pushAddressedPlayerChangedLocked", e); @@ -985,35 +937,6 @@ public class MediaSessionService extends SystemService implements Monitor { ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession(); } - private int getComponentType(@Nullable ComponentName componentName) { - if (componentName == null) { - return COMPONENT_TYPE_INVALID; - } - PackageManager pm = mContext.getPackageManager(); - try { - ActivityInfo activityInfo = pm.getActivityInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.GET_ACTIVITIES); - if (activityInfo != null) { - return COMPONENT_TYPE_ACTIVITY; - } - } catch (NameNotFoundException e) { - } - try { - ServiceInfo serviceInfo = pm.getServiceInfo(componentName, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.GET_SERVICES); - if (serviceInfo != null) { - return COMPONENT_TYPE_SERVICE; - } - } catch (NameNotFoundException e) { - } - // Pick legacy behavior for BroadcastReceiver or unknown. - return COMPONENT_TYPE_BROADCAST; - } - final class OnMediaKeyEventDispatchedListenerRecord implements IBinder.DeathRecipient { public final IOnMediaKeyEventDispatchedListener callback; public final int uid; @@ -2166,79 +2089,31 @@ public class MediaSessionService extends SystemService implements Monitor { } catch (RemoteException e) { Log.w(TAG, "Failed to send callback", e); } - } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null - || mCurrentFullUserRecord.mRestoredMediaButtonReceiver != null) { + } else if (mCurrentFullUserRecord.mLastMediaButtonReceiverHolder != null) { if (needWakeLock) { mKeyEventReceiver.acquireWakeLockLocked(); } - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - // TODO: Find a way to also send PID/UID in secure way. - String callerPackageName = + String callingPackageName = (asSystemService) ? mContext.getPackageName() : packageName; - mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callerPackageName); - try { - if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) { - PendingIntent receiver = mCurrentFullUserRecord.mLastMediaButtonReceiver; - if (DEBUG_KEY_EVENT) { - Log.d(TAG, "Sending " + keyEvent - + " to the last known PendingIntent " + receiver); - } - receiver.send(mContext, - needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, - mediaButtonIntent, mKeyEventReceiver, mHandler); - ComponentName componentName = mCurrentFullUserRecord - .mLastMediaButtonReceiver.getIntent().getComponent(); - if (componentName != null) { - for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr - : mCurrentFullUserRecord - .mOnMediaKeyEventDispatchedListeners.values()) { - cr.callback.onMediaKeyEventDispatched(keyEvent, - componentName.getPackageName(), null); - } - } - } else { - ComponentName receiver = - mCurrentFullUserRecord.mRestoredMediaButtonReceiver; - int componentType = mCurrentFullUserRecord - .mRestoredMediaButtonReceiverComponentType; - UserHandle userHandle = UserHandle.of(mCurrentFullUserRecord - .mRestoredMediaButtonReceiverUserId); - if (DEBUG_KEY_EVENT) { - Log.d(TAG, "Sending " + keyEvent + " to the restored intent " - + receiver + ", type=" + componentType); - } - mediaButtonIntent.setComponent(receiver); + + MediaButtonReceiverHolder mediaButtonReceiverHolder = + mCurrentFullUserRecord.mLastMediaButtonReceiverHolder; + + boolean sent = mediaButtonReceiverHolder.send( + mContext, keyEvent, callingPackageName, + needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver, + mHandler); + if (sent) { + String pkgName = mediaButtonReceiverHolder.getPackageName(); + for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr + : mCurrentFullUserRecord + .mOnMediaKeyEventDispatchedListeners.values()) { try { - switch (componentType) { - case FullUserRecord.COMPONENT_TYPE_ACTIVITY: - mContext.startActivityAsUser(mediaButtonIntent, userHandle); - break; - case FullUserRecord.COMPONENT_TYPE_SERVICE: - mContext.startForegroundServiceAsUser(mediaButtonIntent, - userHandle); - break; - default: - // Legacy behavior for other cases. - mContext.sendBroadcastAsUser(mediaButtonIntent, userHandle); - } - } catch (Exception e) { - Log.w(TAG, "Error sending media button to the restored intent " - + receiver + ", type=" + componentType, e); - } - for (FullUserRecord.OnMediaKeyEventDispatchedListenerRecord cr - : mCurrentFullUserRecord - .mOnMediaKeyEventDispatchedListeners.values()) { - cr.callback.onMediaKeyEventDispatched(keyEvent, - receiver.getPackageName(), null); + cr.callback.onMediaKeyEventDispatched(keyEvent, pkgName, null); + } catch (RemoteException e) { + Log.w(TAG, "Failed notify key event dispatch, uid=" + cr.uid, e); } } - } catch (CanceledException e) { - Log.i(TAG, "Error sending key event to media button receiver " - + mCurrentFullUserRecord.mLastMediaButtonReceiver, e); - } catch (RemoteException e) { - Log.w(TAG, "Failed to send callback", e); } } } diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java index e8cb1632bdc3..061cbd8a1a40 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java @@ -170,6 +170,11 @@ public class NotificationHistoryDatabase { mFileWriteHandler.post(rpr); } + public void deleteNotificationHistoryItem(String pkg, long postedTime) { + RemoveNotificationRunnable rnr = new RemoveNotificationRunnable(pkg, postedTime); + mFileWriteHandler.post(rnr); + } + public void addNotification(final HistoricalNotification notification) { synchronized (mLock) { mBuffer.addNewNotificationToWrite(notification); @@ -367,6 +372,49 @@ public class NotificationHistoryDatabase { } } + final class RemoveNotificationRunnable implements Runnable { + private String mPkg; + private long mPostedTime; + private NotificationHistory mNotificationHistory; + + public RemoveNotificationRunnable(String pkg, long postedTime) { + mPkg = pkg; + mPostedTime = postedTime; + } + + @VisibleForTesting + void setNotificationHistory(NotificationHistory nh) { + mNotificationHistory = nh; + } + + @Override + public void run() { + if (DEBUG) Slog.d(TAG, "RemovePackageRunnable"); + synchronized (mLock) { + // Remove from pending history + mBuffer.removeNotificationFromWrite(mPkg, mPostedTime); + + Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator(); + while (historyFileItr.hasNext()) { + final AtomicFile af = historyFileItr.next(); + try { + NotificationHistory notificationHistory = mNotificationHistory != null + ? mNotificationHistory + : new NotificationHistory(); + readLocked(af, notificationHistory, + new NotificationHistoryFilter.Builder().build()); + if(notificationHistory.removeNotificationFromWrite(mPkg, mPostedTime)) { + writeLocked(af, notificationHistory); + } + } catch (Exception e) { + Slog.e(TAG, "Cannot clean up file on notification removal " + + af.getBaseFile().getName(), e); + } + } + } + } + } + public static final class NotificationHistoryFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider { final static String TAG = "NotifHistoryFileDate"; diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java index 41bc29f7a82e..9aab0fd912e8 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java @@ -130,7 +130,7 @@ public class NotificationHistoryManager { } } - public void onPackageRemoved(int userId, String packageName) { + public void onPackageRemoved(@UserIdInt int userId, String packageName) { synchronized (mLock) { if (!mUserUnlockedStates.get(userId, false)) { if (mHistoryEnabled.get(userId, false)) { @@ -150,6 +150,22 @@ public class NotificationHistoryManager { } } + public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) { + synchronized (mLock) { + int userId = UserHandle.getUserId(uid); + final NotificationHistoryDatabase userHistory = + getUserHistoryAndInitializeIfNeededLocked(userId); + // TODO: it shouldn't be possible to delete a notification entry while the user is + // locked but we should handle it + if (userHistory == null) { + Slog.w(TAG, "Attempted to remove notif for locked/gone/disabled user " + + userId); + return; + } + userHistory.deleteNotificationHistoryItem(pkg, postedTime); + } + } + // TODO: wire this up to AMS when power button is long pressed public void triggerWriteToDisk() { synchronized (mLock) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f07113591fa5..7ffd899215de 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -193,6 +193,7 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.service.notification.Adjustment; import android.service.notification.Condition; +import android.service.notification.ConversationChannelWrapper; import android.service.notification.IConditionProvider; import android.service.notification.INotificationListener; import android.service.notification.IStatusBarNotificationHolder; @@ -2780,7 +2781,7 @@ public class NotificationManagerService extends SystemService { if (!isSystemToast) { int count = 0; final int N = mToastQueue.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { final ToastRecord r = mToastQueue.get(i); if (r.pkg.equals(pkg)) { count++; @@ -2820,7 +2821,7 @@ public class NotificationManagerService extends SystemService { if (pkg == null || token == null) { Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " token=" + token); - return ; + return; } synchronized (mToastQueue) { @@ -2933,14 +2934,14 @@ public class NotificationManagerService extends SystemService { /** * Updates the enabled state for notifications for the given package (and uid). - * Additionally, this method marks the app importance as locked by the user, which means + * Additionally, this method marks the app importance as locked by the user, which + * means * that notifications from the app will <b>not</b> be considered for showing a * blocking helper. * - * @param pkg package that owns the notifications to update - * @param uid uid of the app providing notifications + * @param pkg package that owns the notifications to update + * @param uid uid of the app providing notifications * @param enabled whether notifications should be enabled for the app - * * @see #setNotificationsEnabledForPackage(String, int, boolean) */ @Override @@ -3031,6 +3032,12 @@ public class NotificationManagerService extends SystemService { } @Override + public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) { + checkCallerIsSystem(); + mHistoryManager.deleteNotificationHistoryItem(pkg, uid, postedTime); + } + + @Override public int getPackageImportance(String pkg) { checkCallerIsSystemOrSameApp(pkg); return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid()); @@ -3215,9 +3222,10 @@ public class NotificationManagerService extends SystemService { @Override public NotificationChannel getNotificationChannelForPackage(String pkg, int uid, - String channelId, boolean includeDeleted) { + String channelId, String conversationId, boolean includeDeleted) { checkCallerIsSystem(); - return mPreferencesHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted); + return mPreferencesHelper.getConversationNotificationChannel( + pkg, uid, channelId, conversationId, true, includeDeleted); } @Override @@ -3354,6 +3362,27 @@ public class NotificationManagerService extends SystemService { } @Override + public ParceledListSlice<ConversationChannelWrapper> getConversationsForPackage(String pkg, + int uid) { + enforceSystemOrSystemUI("getConversationsForPackage"); + ArrayList<ConversationChannelWrapper> conversations = + mPreferencesHelper.getConversations(pkg, uid); + for (ConversationChannelWrapper conversation : conversations) { + LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery() + .setPackage(pkg) + .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED) + .setShortcutIds(Arrays.asList( + conversation.getNotificationChannel().getConversationId())); + List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts( + query, UserHandle.of(UserHandle.getUserId(uid))); + if (shortcuts != null && !shortcuts.isEmpty()) { + conversation.setShortcutInfo(shortcuts.get(0)); + } + } + return new ParceledListSlice<>(conversations); + } + + @Override public NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage( String pkg, int uid, String groupId, boolean includeDeleted) { enforceSystemOrSystemUI("getPopulatedNotificationChannelGroupForPackage"); @@ -7434,13 +7463,15 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mNotificationLock") - private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason, + private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, + @NotificationListenerService.NotificationCancelReason int reason, boolean wasPosted, String listenerName) { cancelNotificationLocked(r, sendDelete, reason, -1, -1, wasPosted, listenerName); } @GuardedBy("mNotificationLock") - private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason, + private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, + @NotificationListenerService.NotificationCancelReason int reason, int rank, int count, boolean wasPosted, String listenerName) { final String canceledKey = r.getKey(); @@ -7558,6 +7589,10 @@ public class NotificationManagerService extends SystemService { EventLogTags.writeNotificationCanceled(canceledKey, reason, r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now), rank, count, listenerName); + if (wasPosted) { + mNotificationRecordLogger.logNotificationCancelled(r, reason, + r.getStats().getDismissalSurface()); + } } @VisibleForTesting @@ -9256,7 +9291,7 @@ public class NotificationManagerService extends SystemService { } BackgroundThread.getHandler().post(() -> { - if (hasCompanionDevice(serviceInfo)) { + if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) { notifyNotificationChannelChanged( serviceInfo, pkg, user, channel, modificationType); } @@ -9276,7 +9311,7 @@ public class NotificationManagerService extends SystemService { } BackgroundThread.getHandler().post(() -> { - if (hasCompanionDevice(serviceInfo)) { + if (serviceInfo.isSystem || hasCompanionDevice(serviceInfo)) { notifyNotificationChannelGroupChanged( serviceInfo, pkg, user, group, modificationType); } diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index 8d8511f030f0..fc2d9e775149 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -16,10 +16,16 @@ package com.android.server.notification; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; +import static android.service.notification.NotificationListenerService.REASON_CLICK; +import static android.service.notification.NotificationListenerService.REASON_TIMEOUT; + import android.annotation.Nullable; import android.app.Notification; import android.app.Person; import android.os.Bundle; +import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationStats; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; @@ -44,22 +50,144 @@ public interface NotificationRecordLogger { int position, int buzzBeepBlink); /** + * Logs a notification cancel / dismiss event using UiEventReported (event ids from the + * NotificationCancelledEvents enum). + * @param r The NotificationRecord. If null, no action is taken. + * @param reason The reason the notification was canceled. + * @param dismissalSurface The surface the notification was dismissed from. + */ + void logNotificationCancelled(@Nullable NotificationRecord r, + @NotificationListenerService.NotificationCancelReason int reason, + @NotificationStats.DismissalSurface int dismissalSurface); + + /** * The UiEvent enums that this class can log. */ - enum NotificationReportedEvents implements UiEventLogger.UiEventEnum { - INVALID(0), + enum NotificationReportedEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "New notification enqueued to post") NOTIFICATION_POSTED(162), - @UiEvent(doc = "Notification substantially updated") + @UiEvent(doc = "Notification substantially updated, or alerted again.") NOTIFICATION_UPDATED(163); private final int mId; - NotificationReportedEvents(int id) { + NotificationReportedEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } + + public static NotificationReportedEvent fromRecordPair(NotificationRecordPair p) { + return (p.old != null) ? NotificationReportedEvent.NOTIFICATION_UPDATED : + NotificationReportedEvent.NOTIFICATION_POSTED; + } + } + + enum NotificationCancelledEvent implements UiEventLogger.UiEventEnum { + INVALID(0), + @UiEvent(doc = "Notification was canceled due to a notification click.") + NOTIFICATION_CANCEL_CLICK(164), + @UiEvent(doc = "Notification was canceled due to a user dismissal, surface not specified.") + NOTIFICATION_CANCEL_USER_OTHER(165), + @UiEvent(doc = "Notification was canceled due to a user dismiss-all (from the notification" + + " shade).") + NOTIFICATION_CANCEL_USER_CANCEL_ALL(166), + @UiEvent(doc = "Notification was canceled due to an inflation error.") + NOTIFICATION_CANCEL_ERROR(167), + @UiEvent(doc = "Notification was canceled by the package manager modifying the package.") + NOTIFICATION_CANCEL_PACKAGE_CHANGED(168), + @UiEvent(doc = "Notification was canceled by the owning user context being stopped.") + NOTIFICATION_CANCEL_USER_STOPPED(169), + @UiEvent(doc = "Notification was canceled by the user banning the package.") + NOTIFICATION_CANCEL_PACKAGE_BANNED(170), + @UiEvent(doc = "Notification was canceled by the app canceling this specific notification.") + NOTIFICATION_CANCEL_APP_CANCEL(171), + @UiEvent(doc = "Notification was canceled by the app cancelling all its notifications.") + NOTIFICATION_CANCEL_APP_CANCEL_ALL(172), + @UiEvent(doc = "Notification was canceled by a listener reporting a user dismissal.") + NOTIFICATION_CANCEL_LISTENER_CANCEL(173), + @UiEvent(doc = "Notification was canceled by a listener reporting a user dismiss all.") + NOTIFICATION_CANCEL_LISTENER_CANCEL_ALL(174), + @UiEvent(doc = "Notification was canceled because it was a member of a canceled group.") + NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED(175), + @UiEvent(doc = "Notification was canceled because it was an invisible member of a group.") + NOTIFICATION_CANCEL_GROUP_OPTIMIZATION(176), + @UiEvent(doc = "Notification was canceled by the device administrator suspending the " + + "package.") + NOTIFICATION_CANCEL_PACKAGE_SUSPENDED(177), + @UiEvent(doc = "Notification was canceled by the owning managed profile being turned off.") + NOTIFICATION_CANCEL_PROFILE_TURNED_OFF(178), + @UiEvent(doc = "Autobundled summary notification was canceled because its group was " + + "unbundled") + NOTIFICATION_CANCEL_UNAUTOBUNDLED(179), + @UiEvent(doc = "Notification was canceled by the user banning the channel.") + NOTIFICATION_CANCEL_CHANNEL_BANNED(180), + @UiEvent(doc = "Notification was snoozed.") + NOTIFICATION_CANCEL_SNOOZED(181), + @UiEvent(doc = "Notification was canceled due to timeout") + NOTIFICATION_CANCEL_TIMEOUT(182), + // Values 183-189 reserved for future system dismissal reasons + @UiEvent(doc = "Notification was canceled due to user dismissal of a peeking notification.") + NOTIFICATION_CANCEL_USER_PEEK(190), + @UiEvent(doc = "Notification was canceled due to user dismissal from the always-on display") + NOTIFICATION_CANCEL_USER_AOD(191), + @UiEvent(doc = "Notification was canceled due to user dismissal from the notification" + + " shade.") + NOTIFICATION_CANCEL_USER_SHADE(192), + @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen") + NOTIFICATION_CANCEL_USER_LOCKSCREEN(193); + + private final int mId; + NotificationCancelledEvent(int id) { mId = id; } @Override public int getId() { return mId; } + public static NotificationCancelledEvent fromCancelReason( + @NotificationListenerService.NotificationCancelReason int reason, + @NotificationStats.DismissalSurface int surface) { + // Shouldn't be possible to get a non-dismissed notification here. + if (surface == NotificationStats.DISMISSAL_NOT_DISMISSED) { + if (NotificationManagerService.DBG) { + throw new IllegalArgumentException("Unexpected surface " + surface); + } + return INVALID; + } + // Most cancel reasons do not have a meaningful surface. Reason codes map directly + // to NotificationCancelledEvent codes. + if (surface == NotificationStats.DISMISSAL_OTHER) { + if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) { + return NotificationCancelledEvent.values()[reason]; + } + if (NotificationManagerService.DBG) { + throw new IllegalArgumentException("Unexpected cancel reason " + reason); + } + return INVALID; + } + // User cancels have a meaningful surface, which we differentiate by. See b/149038335 + // for caveats. + if (reason != REASON_CANCEL) { + if (NotificationManagerService.DBG) { + throw new IllegalArgumentException("Unexpected cancel with surface " + reason); + } + return INVALID; + } + switch (surface) { + case NotificationStats.DISMISSAL_PEEK: + return NOTIFICATION_CANCEL_USER_PEEK; + case NotificationStats.DISMISSAL_AOD: + return NOTIFICATION_CANCEL_USER_AOD; + case NotificationStats.DISMISSAL_SHADE: + return NOTIFICATION_CANCEL_USER_SHADE; + default: + if (NotificationManagerService.DBG) { + throw new IllegalArgumentException("Unexpected surface for user-dismiss " + + reason); + } + return INVALID; + } + } } /** @@ -88,7 +216,8 @@ public interface NotificationRecordLogger { return true; } - return !(Objects.equals(r.getSbn().getChannelIdLogTag(), old.getSbn().getChannelIdLogTag()) + return !(Objects.equals(r.getSbn().getChannelIdLogTag(), + old.getSbn().getChannelIdLogTag()) && Objects.equals(r.getSbn().getGroupLogTag(), old.getSbn().getGroupLogTag()) && (r.getSbn().getNotification().isGroupSummary() == old.getSbn().getNotification().isGroupSummary()) @@ -97,11 +226,6 @@ public interface NotificationRecordLogger { && (r.getImportance() == old.getImportance())); } - NotificationReportedEvents getUiEvent() { - return (old != null) ? NotificationReportedEvents.NOTIFICATION_UPDATED : - NotificationReportedEvents.NOTIFICATION_POSTED; - } - /** * @return hash code for the notification style class, or 0 if none exists. */ diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java index 4974c3001b9b..015d280535e6 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java @@ -16,6 +16,8 @@ package com.android.server.notification; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.util.FrameworkStatsLog; /** @@ -24,6 +26,8 @@ import com.android.internal.util.FrameworkStatsLog; */ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { + UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + @Override public void logNotificationReported(NotificationRecord r, NotificationRecord old, int position, int buzzBeepBlink) { @@ -32,7 +36,7 @@ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { return; } FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED, - /* int32 event_id = 1 */ p.getUiEvent().getId(), + /* int32 event_id = 1 */ NotificationReportedEvent.fromRecordPair(p).getId(), /* int32 uid = 2 */ r.getUid(), /* string package_name = 3 */ r.getSbn().getPackageName(), /* int32 instance_id = 4 */ p.getInstanceId(), @@ -61,9 +65,10 @@ public class NotificationRecordLoggerImpl implements NotificationRecordLogger { ); } - - - - - + @Override + public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) { + mUiEventLogger.logWithInstanceId( + NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), + r.getUid(), r.getSbn().getPackageName(), r.getSbn().getInstanceId()); + } } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index fe39322edb42..a244c48fab6a 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -36,6 +36,7 @@ import android.metrics.LogMaker; import android.os.Build; import android.os.UserHandle; import android.provider.Settings; +import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerService; import android.service.notification.RankingHelperProto; import android.text.TextUtils; @@ -1176,6 +1177,43 @@ public class PreferencesHelper implements RankingConfig { return groups; } + public ArrayList<ConversationChannelWrapper> getConversations(String pkg, int uid) { + Objects.requireNonNull(pkg); + synchronized (mPackagePreferences) { + PackagePreferences r = getPackagePreferencesLocked(pkg, uid); + if (r == null) { + return new ArrayList<>(); + } + ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>(); + int N = r.channels.size(); + for (int i = 0; i < N; i++) { + final NotificationChannel nc = r.channels.valueAt(i); + if (!TextUtils.isEmpty(nc.getConversationId()) && !nc.isDeleted()) { + ConversationChannelWrapper conversation = new ConversationChannelWrapper(); + conversation.setNotificationChannel(nc); + conversation.setParentChannelLabel( + r.channels.get(nc.getParentChannelId()).getName()); + boolean blockedByGroup = false; + if (nc.getGroup() != null) { + NotificationChannelGroup group = r.groups.get(nc.getGroup()); + if (group != null) { + if (group.isBlocked()) { + blockedByGroup = true; + } else { + conversation.setGroupLabel(group.getName()); + } + } + } + if (!blockedByGroup) { + conversations.add(conversation); + } + } + } + + return conversations; + } + } + @Override public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted) { diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 3ad120705eb3..d629b547992b 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -33,7 +33,7 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedComponent; import android.content.pm.parsing.ComponentParseUtils.ParsedIntentInfo; import android.content.pm.parsing.ComponentParseUtils.ParsedProvider; import android.content.pm.parsing.ComponentParseUtils.ParsedService; -import android.net.Uri; +import android.os.Binder; import android.os.Process; import android.os.Trace; import android.os.UserHandle; @@ -86,10 +86,10 @@ public class AppsFilter { private final SparseSetArray<Integer> mQueriesViaPackage = new SparseSetArray<>(); /** - * A mapping from the set of App IDs that query others via intent to the list - * of packages that the intents resolve to. + * A mapping from the set of App IDs that query others via component match to the list + * of packages that the they resolve to. */ - private final SparseSetArray<Integer> mQueriesViaIntent = new SparseSetArray<>(); + private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>(); /** * A set of App IDs that are always queryable by any package, regardless of their manifest @@ -171,11 +171,13 @@ public class AppsFilter { @Override public boolean packageIsEnabled(AndroidPackage pkg) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "packageIsEnabled"); + final long token = Binder.clearCallingIdentity(); try { // TODO(b/135203078): Do not use toAppInfo return mInjector.getCompatibility().isChangeEnabled( PackageManager.FILTER_APPLICATION_QUERY, pkg.toAppInfoWithoutState()); } finally { + Binder.restoreCallingIdentity(token); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } @@ -203,16 +205,19 @@ public class AppsFilter { } /** Returns true if the querying package may query for the potential target package */ - private static boolean canQueryViaIntent(AndroidPackage querying, + private static boolean canQueryViaComponents(AndroidPackage querying, AndroidPackage potentialTarget) { - if (querying.getQueriesIntents() == null) { - return false; - } - for (Intent intent : querying.getQueriesIntents()) { - if (matches(intent, potentialTarget)) { - return true; + if (querying.getQueriesIntents() != null) { + for (Intent intent : querying.getQueriesIntents()) { + if (matchesIntentFilters(intent, potentialTarget)) { + return true; + } } } + if (querying.getQueriesProviders() != null + && matchesProviders(querying.getQueriesProviders(), potentialTarget)) { + return true; + } return false; } @@ -235,24 +240,25 @@ public class AppsFilter { return false; } - private static boolean matches(Intent intent, AndroidPackage potentialTarget) { + private static boolean matchesProviders( + Set<String> queriesAuthorities, AndroidPackage potentialTarget) { for (int p = ArrayUtils.size(potentialTarget.getProviders()) - 1; p >= 0; p--) { ParsedProvider provider = potentialTarget.getProviders().get(p); if (!provider.isExported()) { continue; } - final Uri data = intent.getData(); - if (!"content".equalsIgnoreCase(intent.getScheme()) || data == null - || provider.getAuthority() == null) { - continue; - } - StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", false); + StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", + false); while (authorities.hasMoreElements()) { - if (Objects.equals(authorities.nextElement(), data.getAuthority())) { + if (queriesAuthorities.contains(authorities.nextToken())) { return true; } } } + return false; + } + + private static boolean matchesIntentFilters(Intent intent, AndroidPackage potentialTarget) { for (int s = ArrayUtils.size(potentialTarget.getServices()) - 1; s >= 0; s--) { ParsedService service = potentialTarget.getServices().get(s); if (!service.exported) { @@ -365,8 +371,8 @@ public class AppsFilter { final AndroidPackage existingPkg = existingSetting.pkg; // let's evaluate the ability of already added packages to see this new package if (!newIsForceQueryable) { - if (canQueryViaIntent(existingPkg, newPkg)) { - mQueriesViaIntent.add(existingSetting.appId, newPkgSetting.appId); + if (canQueryViaComponents(existingPkg, newPkg)) { + mQueriesViaComponent.add(existingSetting.appId, newPkgSetting.appId); } if (canQueryViaPackage(existingPkg, newPkg) || canQueryAsInstaller(existingSetting, newPkg)) { @@ -375,8 +381,8 @@ public class AppsFilter { } // now we'll evaluate our new package's ability to see existing packages if (!mForceQueryable.contains(existingSetting.appId)) { - if (canQueryViaIntent(newPkg, existingPkg)) { - mQueriesViaIntent.add(newPkgSetting.appId, existingSetting.appId); + if (canQueryViaComponents(newPkg, existingPkg)) { + mQueriesViaComponent.add(newPkgSetting.appId, existingSetting.appId); } if (canQueryViaPackage(newPkg, existingPkg) || canQueryAsInstaller(newPkgSetting, existingPkg)) { @@ -424,9 +430,9 @@ public class AppsFilter { } } - mQueriesViaIntent.remove(setting.appId); - for (int i = mQueriesViaIntent.size() - 1; i >= 0; i--) { - mQueriesViaIntent.remove(mQueriesViaIntent.keyAt(i), setting.appId); + mQueriesViaComponent.remove(setting.appId); + for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) { + mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId); } mQueriesViaPackage.remove(setting.appId); for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) { @@ -591,10 +597,10 @@ public class AppsFilter { Trace.endSection(); } try { - Trace.beginSection("mQueriesViaIntent"); - if (mQueriesViaIntent.contains(callingAppId, targetAppId)) { + Trace.beginSection("mQueriesViaComponent"); + if (mQueriesViaComponent.contains(callingAppId, targetAppId)) { if (DEBUG_LOGGING) { - log(callingSetting, targetPkgSetting, "queries intent"); + log(callingSetting, targetPkgSetting, "queries component"); } return false; } @@ -729,7 +735,7 @@ public class AppsFilter { pw.println(" queries via package name:"); dumpQueriesMap(pw, filteringAppId, mQueriesViaPackage, " ", expandPackages); pw.println(" queries via intent:"); - dumpQueriesMap(pw, filteringAppId, mQueriesViaIntent, " ", expandPackages); + dumpQueriesMap(pw, filteringAppId, mQueriesViaComponent, " ", expandPackages); pw.println(" queryable via interaction:"); for (int user : users) { pw.append(" User ").append(Integer.toString(user)).println(":"); diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 3e64e9828c3e..e9f84fd419d6 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import android.Manifest.permission; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; @@ -305,6 +306,10 @@ public class LauncherAppsService extends SystemService { final int callingUserId = injectCallingUserId(); if (targetUserId == callingUserId) return true; + if (mContext.checkCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL) + == PackageManager.PERMISSION_GRANTED) { + return true; + } long ident = injectClearCallingIdentity(); try { @@ -661,6 +666,23 @@ public class LauncherAppsService extends SystemService { } } + private void ensureStrictAccessShortcutsPermission(@NonNull String callingPackage) { + verifyCallingPackage(callingPackage); + if (!injectHasAccessShortcutsPermission(injectBinderCallingPid(), + injectBinderCallingUid())) { + throw new SecurityException("Caller can't access shortcut information"); + } + } + + /** + * Returns true if the caller has the "ACCESS_SHORTCUTS" permission. + */ + @VisibleForTesting + boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) { + return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS, + callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; + } + @Override public ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName, List shortcutIds, List<LocusId> locusIds, @@ -710,6 +732,30 @@ public class LauncherAppsService extends SystemService { } @Override + public void cacheShortcuts(String callingPackage, String packageName, List<String> ids, + UserHandle targetUser) { + ensureStrictAccessShortcutsPermission(callingPackage); + if (!canAccessProfile(targetUser.getIdentifier(), "Cannot cache shortcuts")) { + return; + } + + mShortcutServiceInternal.cacheShortcuts(getCallingUserId(), + callingPackage, packageName, ids, targetUser.getIdentifier()); + } + + @Override + public void uncacheShortcuts(String callingPackage, String packageName, List<String> ids, + UserHandle targetUser) { + ensureStrictAccessShortcutsPermission(callingPackage); + if (!canAccessProfile(targetUser.getIdentifier(), "Cannot uncache shortcuts")) { + return; + } + + mShortcutServiceInternal.uncacheShortcuts(getCallingUserId(), + callingPackage, packageName, ids, targetUser.getIdentifier()); + } + + @Override public int getShortcutIconResId(String callingPackage, String packageName, String id, int targetUserId) { ensureShortcutPermission(callingPackage); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 944280d6db88..97defcdd3bc7 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -214,7 +214,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill"; private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT; - private static final FileInfo[] EMPTY_FILE_INFO_ARRAY = {}; + private static final InstallationFile[] EMPTY_INSTALLATION_FILE_ARRAY = {}; private static final String SYSTEM_DATA_LOADER_PACKAGE = "android"; @@ -309,33 +309,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private int mParentSessionId; - static class FileInfo { - public final int location; - public final String name; - public final Long lengthBytes; - public final byte[] metadata; - public final byte[] signature; - - public static FileInfo added(int location, String name, Long lengthBytes, byte[] metadata, - byte[] signature) { - return new FileInfo(location, name, lengthBytes, metadata, signature); - } - - public static FileInfo removed(int location, String name) { - return new FileInfo(location, name, -1L, null, null); - } - - FileInfo(int location, String name, Long lengthBytes, byte[] metadata, byte[] signature) { - this.location = location; - this.name = name; - this.lengthBytes = lengthBytes; - this.metadata = metadata; - this.signature = signature; - } - } - @GuardedBy("mLock") - private ArrayList<FileInfo> mFiles = new ArrayList<>(); + private ArrayList<InstallationFile> mFiles = new ArrayList<>(); @GuardedBy("mLock") private boolean mStagedSessionApplied; @@ -508,7 +483,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager, int sessionId, int userId, int installerUid, @NonNull InstallSource installSource, SessionParams params, long createdMillis, - File stageDir, String stageCid, FileInfo[] files, boolean prepared, + File stageDir, String stageCid, InstallationFile[] files, boolean prepared, boolean committed, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, boolean isFailed, boolean isApplied, int stagedSessionErrorCode, @@ -539,7 +514,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { this.mParentSessionId = parentSessionId; if (files != null) { - for (FileInfo file : files) { + for (InstallationFile file : files) { mFiles.add(file); } } @@ -738,7 +713,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { String[] result = new String[mFiles.size()]; for (int i = 0, size = mFiles.size(); i < size; ++i) { - result[i] = mFiles.get(i).name; + result[i] = mFiles.get(i).getName(); } return result; } @@ -2425,7 +2400,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("addFile"); - mFiles.add(FileInfo.added(location, name, lengthBytes, metadata, signature)); + mFiles.add(new InstallationFile(location, name, lengthBytes, metadata, signature)); } } @@ -2443,7 +2418,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("removeFile"); - mFiles.add(FileInfo.removed(location, getRemoveMarkerName(name))); + mFiles.add(new InstallationFile(location, getRemoveMarkerName(name), -1, null, null)); } } @@ -2461,17 +2436,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } final List<InstallationFile> addedFiles = new ArrayList<>(mFiles.size()); - for (FileInfo file : mFiles) { - if (sAddedFilter.accept(new File(this.stageDir, file.name))) { - addedFiles.add(new InstallationFile( - file.name, file.lengthBytes, file.metadata)); + for (InstallationFile file : mFiles) { + if (sAddedFilter.accept(new File(this.stageDir, file.getName()))) { + addedFiles.add(file); } } final List<String> removedFiles = new ArrayList<>(mFiles.size()); - for (FileInfo file : mFiles) { - if (sRemovedFilter.accept(new File(this.stageDir, file.name))) { - String name = file.name.substring( - 0, file.name.length() - REMOVE_MARKER_EXTENSION.length()); + for (InstallationFile file : mFiles) { + if (sRemovedFilter.accept(new File(this.stageDir, file.getName()))) { + String name = file.getName().substring( + 0, file.getName().length() - REMOVE_MARKER_EXTENSION.length()); removedFiles.add(name); } } @@ -2970,13 +2944,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeIntAttribute(out, ATTR_SESSION_ID, childSessionId); out.endTag(null, TAG_CHILD_SESSION); } - for (FileInfo fileInfo : mFiles) { + for (InstallationFile file : mFiles) { out.startTag(null, TAG_SESSION_FILE); - writeIntAttribute(out, ATTR_LOCATION, fileInfo.location); - writeStringAttribute(out, ATTR_NAME, fileInfo.name); - writeLongAttribute(out, ATTR_LENGTH_BYTES, fileInfo.lengthBytes); - writeByteArrayAttribute(out, ATTR_METADATA, fileInfo.metadata); - writeByteArrayAttribute(out, ATTR_SIGNATURE, fileInfo.signature); + writeIntAttribute(out, ATTR_LOCATION, file.getLocation()); + writeStringAttribute(out, ATTR_NAME, file.getName()); + writeLongAttribute(out, ATTR_LENGTH_BYTES, file.getLengthBytes()); + writeByteArrayAttribute(out, ATTR_METADATA, file.getMetadata()); + writeByteArrayAttribute(out, ATTR_SIGNATURE, file.getSignature()); out.endTag(null, TAG_SESSION_FILE); } } @@ -3088,7 +3062,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { List<String> grantedRuntimePermissions = new ArrayList<>(); List<String> whitelistedRestrictedPermissions = new ArrayList<>(); List<Integer> childSessionIds = new ArrayList<>(); - List<FileInfo> files = new ArrayList<>(); + List<InstallationFile> files = new ArrayList<>(); int outerDepth = in.getDepth(); int type; while ((type = in.next()) != XmlPullParser.END_DOCUMENT @@ -3107,7 +3081,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID)); } if (TAG_SESSION_FILE.equals(in.getName())) { - files.add(new FileInfo( + files.add(new InstallationFile( readIntAttribute(in, ATTR_LOCATION, 0), readStringAttribute(in, ATTR_NAME), readLongAttribute(in, ATTR_LENGTH_BYTES, -1), @@ -3135,16 +3109,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY; } - FileInfo[] fileInfosArray = null; + InstallationFile[] fileArray = null; if (!files.isEmpty()) { - fileInfosArray = files.toArray(EMPTY_FILE_INFO_ARRAY); + fileArray = files.toArray(EMPTY_INSTALLATION_FILE_ARRAY); } InstallSource installSource = InstallSource.create(installInitiatingPackageName, installOriginatingPackageName, installerPackageName); return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerUid, - installSource, params, createdMillis, stageDir, stageCid, fileInfosArray, + installSource, params, createdMillis, stageDir, stageCid, fileArray, prepared, committed, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage); } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index cb9404397f3d..c69a62d406aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -1163,7 +1163,7 @@ class PackageManagerShellCommand extends ShellCommand { final InstallParams params = makeInstallParams(); if (params.sessionParams.dataLoaderParams == null) { params.sessionParams.setDataLoaderParams( - PackageManagerShellCommandDataLoader.getDataLoaderParams(this)); + PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(this)); } return doRunInstall(params); } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java index 5dca9e147e31..4170be4a913d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java @@ -54,7 +54,7 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { private static final String STDIN_PATH = "-"; - static DataLoaderParams getDataLoaderParams(ShellCommand shellCommand) { + private static String getDataLoaderParamsArgs(ShellCommand shellCommand) { int commandId; synchronized (sShellCommands) { // Clean up old references. @@ -78,8 +78,12 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { sShellCommands.put(commandId, new WeakReference<>(shellCommand)); } - final String args = SHELL_COMMAND_ID_PREFIX + commandId; - return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), args); + return SHELL_COMMAND_ID_PREFIX + commandId; + } + + static DataLoaderParams getStreamingDataLoaderParams(ShellCommand shellCommand) { + return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), + getDataLoaderParamsArgs(shellCommand)); } private static int extractShellCommandId(String args) { @@ -133,17 +137,17 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { return false; } try { - for (InstallationFile fileInfo : addedFiles) { - String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8); + for (InstallationFile file : addedFiles) { + String filePath = new String(file.getMetadata(), StandardCharsets.UTF_8); if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) { final ParcelFileDescriptor inFd = ParcelFileDescriptor.dup( shellCommand.getInFileDescriptor()); - mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd); + mConnector.writeData(file.getName(), 0, file.getLengthBytes(), inFd); } else { ParcelFileDescriptor incomingFd = null; try { incomingFd = shellCommand.openFileForSystem(filePath, "r"); - mConnector.writeData(fileInfo.getName(), 0, incomingFd.getStatSize(), + mConnector.writeData(file.getName(), 0, incomingFd.getStatSize(), incomingFd); } finally { IoUtils.closeQuietly(incomingFd); @@ -159,7 +163,8 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { } @Override - public DataLoaderService.DataLoader onCreateDataLoader() { + public DataLoaderService.DataLoader onCreateDataLoader( + @NonNull DataLoaderParams dataLoaderParams) { return new DataLoader(); } } diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index d16c0748ef0e..377fd16d4e19 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1704,7 +1704,7 @@ public class ShortcutService extends IShortcutService.Stub { ShortcutInfo.validateIcon(shortcut.getIcon()); } - shortcut.replaceFlags(0); + shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED); } private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { @@ -2758,6 +2758,68 @@ public class ShortcutService extends IShortcutService.Stub { } @Override + public void cacheShortcuts(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId) { + updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds, + userId, /* doCache= */ true); + } + + @Override + public void uncacheShortcuts(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId) { + updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds, + userId, /* doCache= */ false); + } + + private void updateCachedShortcutsInternal(int launcherUserId, + @NonNull String callingPackage, @NonNull String packageName, + @NonNull List<String> shortcutIds, int userId, boolean doCache) { + // Calling permission must be checked by LauncherAppsImpl. + Preconditions.checkStringNotEmpty(packageName, "packageName"); + Objects.requireNonNull(shortcutIds, "shortcutIds"); + + synchronized (mLock) { + throwIfUserLockedL(userId); + throwIfUserLockedL(launcherUserId); + + final int idSize = shortcutIds.size(); + final ShortcutPackage sp = getUserShortcutsLocked(userId) + .getPackageShortcutsIfExists(packageName); + if (idSize == 0 || sp == null) { + return; + } + + for (int i = 0; i < idSize; i++) { + final String id = Preconditions.checkStringNotEmpty(shortcutIds.get(i)); + final ShortcutInfo si = sp.findShortcutById(id); + if (si == null || doCache == si.isCached()) { + continue; + } + + if (doCache) { + if (si.isDynamic() && si.isLongLived()) { + si.addFlags(ShortcutInfo.FLAG_CACHED); + } else { + Log.w(TAG, "Only dynamic long lived shortcuts can get cached. Ignoring" + + "shortcut " + si.getId()); + } + } else { + if (si.isDynamic()) { + si.clearFlags(ShortcutInfo.FLAG_CACHED); + } else { + sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true); + } + } + } + } + packageShortcutsChanged(packageName, userId); + + verifyStates(); + } + + @Override public Intent[] createShortcutIntents(int launcherUserId, @NonNull String callingPackage, @NonNull String packageName, @NonNull String shortcutId, int userId, diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 2c7795a6274b..43d45967f67b 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -39,9 +39,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal.PackageListObserver; import android.content.pm.PermissionInfo; -import android.content.pm.parsing.AndroidPackage; import android.os.Build; -import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -52,14 +50,15 @@ import android.telecom.TelecomManager; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LongSparseLongArray; -import android.util.Pair; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.infra.AndroidFuture; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.IntPair; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; @@ -70,6 +69,7 @@ import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.ExecutionException; /** @@ -100,7 +100,7 @@ public final class PermissionPolicyService extends SystemService { * scheduled for a package/user. */ @GuardedBy("mLock") - private final ArraySet<Pair<String, Integer>> mIsPackageSyncsScheduled = new ArraySet<>(); + private final ArraySet<Integer> mIsPackageSyncsScheduled = new ArraySet<>(); public PermissionPolicyService(@NonNull Context context) { super(context); @@ -125,10 +125,8 @@ public final class PermissionPolicyService extends SystemService { @Override public void onPackageChanged(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - - if (isStarted(userId)) { - synchronizePackagePermissionsAndAppOpsForUser(packageName, userId); + if (isStarted(UserHandle.getUserId(uid))) { + synchronizePackagePermissionsAndAppOpsForUser(uid); } } @@ -139,12 +137,21 @@ public final class PermissionPolicyService extends SystemService { }); permManagerInternal.addOnRuntimePermissionStateChangedListener( - this::synchronizePackagePermissionsAndAppOpsAsyncForUser); + (packageName, userId) -> { + int uid; + try { + uid = getContext().getPackageManager().getPackageUidAsUser(packageName, 0, + userId); + } catch (NameNotFoundException e) { + Slog.e(LOG_TAG, "Cannot synchronize changed package " + packageName, e); + return; + } + synchronizeUidPermissionsAndAppOpsAsync(uid); + }); mAppOpsCallback = new IAppOpsCallback.Stub() { public void opChanged(int op, int uid, String packageName) { - synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName, - UserHandle.getUserId(uid)); + synchronizeUidPermissionsAndAppOpsAsync(uid); } }; @@ -194,19 +201,17 @@ public final class PermissionPolicyService extends SystemService { return AppOpsManager.opToSwitch(op); } - private void synchronizePackagePermissionsAndAppOpsAsyncForUser(@NonNull String packageName, - @UserIdInt int changedUserId) { - if (isStarted(changedUserId)) { + private void synchronizeUidPermissionsAndAppOpsAsync(int uid) { + if (isStarted(UserHandle.getUserId(uid))) { synchronized (mLock) { - if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) { + if (mIsPackageSyncsScheduled.add(uid)) { FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( PermissionPolicyService ::synchronizePackagePermissionsAndAppOpsForUser, - this, packageName, changedUserId)); + this, uid)); } else { if (DEBUG) { - Slog.v(LOG_TAG, "sync for " + packageName + "/" + changedUserId - + " already scheduled"); + Slog.v(LOG_TAG, "sync for " + uid + " already scheduled"); } } } @@ -335,39 +340,20 @@ public final class PermissionPolicyService extends SystemService { /** * Synchronize a single package. */ - private void synchronizePackagePermissionsAndAppOpsForUser(@NonNull String packageName, - @UserIdInt int userId) { + private void synchronizePackagePermissionsAndAppOpsForUser(int uid) { synchronized (mLock) { - mIsPackageSyncsScheduled.remove(new Pair<>(packageName, userId)); + mIsPackageSyncsScheduled.remove(uid); } if (DEBUG) { Slog.v(LOG_TAG, - "synchronizePackagePermissionsAndAppOpsForUser(" + packageName + ", " - + userId + ")"); + "synchronizePackagePermissionsAndAppOpsForUser(" + uid + ")"); } - final PackageManagerInternal packageManagerInternal = LocalServices.getService( - PackageManagerInternal.class); - final PackageInfo pkg = packageManagerInternal.getPackageInfo(packageName, 0, - Process.SYSTEM_UID, userId); - if (pkg == null) { - return; - } final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser( - getUserContext(getContext(), UserHandle.of(userId))); - synchroniser.addPackage(pkg.packageName); - final String[] sharedPkgNames = packageManagerInternal.getSharedUserPackagesForPackage( - pkg.packageName, userId); - - for (String sharedPkgName : sharedPkgNames) { - final AndroidPackage sharedPkg = packageManagerInternal - .getPackage(sharedPkgName); - if (sharedPkg != null) { - synchroniser.addPackage(sharedPkg.getPackageName()); - } - } - synchroniser.syncPackages(); + getUserContext(getContext(), UserHandle.getUserHandleForUid(uid))); + synchroniser.addUid(uid); + synchroniser.syncUids(); } /** @@ -381,8 +367,8 @@ public final class PermissionPolicyService extends SystemService { final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser( getUserContext(getContext(), UserHandle.of(userId))); packageManagerInternal.forEachPackage( - (pkg) -> synchronizer.addPackage(pkg.getPackageName())); - synchronizer.syncPackages(); + (pkg) -> synchronizer.addUid(pkg.getUid())); + synchronizer.syncUids(); } /** @@ -397,37 +383,51 @@ public final class PermissionPolicyService extends SystemService { private final @NonNull ArrayMap<String, PermissionInfo> mRuntimePermissionInfos; + // Cache uid -> packageNames + private SparseArray<String[]> mUidToPkg = new SparseArray<>(); + /** * All ops that need to be flipped to allow. * - * @see #syncPackages + * @see #syncUids */ - private final @NonNull ArrayList<OpToChange> mOpsToAllow = new ArrayList<>(); + private final @NonNull ArraySet<OpToChange> mOpsToAllow = new ArraySet<>(); /** * All ops that need to be flipped to ignore. * - * @see #syncPackages + * @see #syncUids */ - private final @NonNull ArrayList<OpToChange> mOpsToIgnore = new ArrayList<>(); + private final @NonNull ArraySet<OpToChange> mOpsToIgnore = new ArraySet<>(); /** * All ops that need to be flipped to ignore if not allowed. * * Currently, only used by soft restricted permissions logic. * - * @see #syncPackages + * @see #syncUids */ - private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfNotAllowed = new ArrayList<>(); + private final @NonNull ArraySet<OpToChange> mOpsToIgnoreIfNotAllowed = new ArraySet<>(); /** * All ops that need to be flipped to foreground. * * Currently, only used by the foreground/background permissions logic. * - * @see #syncPackages + * @see #syncUids */ - private final @NonNull ArrayList<OpToChange> mOpsToForeground = new ArrayList<>(); + private final @NonNull ArraySet<OpToChange> mOpsToForeground = new ArraySet<>(); + + private @Nullable String[] getPackageNamesForUid(int uid) { + String[] pkgs = mUidToPkg.get(uid); + if (pkgs != null) { + return pkgs; + } + + pkgs = mPackageManager.getPackagesForUid(uid); + mUidToPkg.put(uid, pkgs); + return pkgs; + } PermissionToOpSynchroniser(@NonNull Context context) { mContext = context; @@ -449,11 +449,11 @@ public final class PermissionPolicyService extends SystemService { } /** - * Set app ops that were added in {@link #addPackage}. + * Set app ops that were added in {@link #addUid}. * * <p>This processes ops previously added by {@link #addAppOps(PackageInfo, String)} */ - private void syncPackages() { + private void syncUids() { // Remember which ops were already set. This makes sure that we always set the most // permissive mode if two OpChanges are scheduled. This can e.g. happen if two // permissions change the same op. See {@link #getSwitchOp}. @@ -461,42 +461,42 @@ public final class PermissionPolicyService extends SystemService { final int allowCount = mOpsToAllow.size(); for (int i = 0; i < allowCount; i++) { - final OpToChange op = mOpsToAllow.get(i); + final OpToChange op = mOpsToAllow.valueAt(i); - setUidModeAllowed(op.code, op.uid, op.packageName); + setUidModeAllowed(op.code, op.uid); alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); } final int foregroundCount = mOpsToForeground.size(); for (int i = 0; i < foregroundCount; i++) { - final OpToChange op = mOpsToForeground.get(i); + final OpToChange op = mOpsToForeground.valueAt(i); if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { continue; } - setUidModeForeground(op.code, op.uid, op.packageName); + setUidModeForeground(op.code, op.uid); alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); } final int ignoreCount = mOpsToIgnore.size(); for (int i = 0; i < ignoreCount; i++) { - final OpToChange op = mOpsToIgnore.get(i); + final OpToChange op = mOpsToIgnore.valueAt(i); if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { continue; } - setUidModeIgnored(op.code, op.uid, op.packageName); + setUidModeIgnored(op.code, op.uid); alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); } final int ignoreIfNotAllowedCount = mOpsToIgnoreIfNotAllowed.size(); for (int i = 0; i < ignoreIfNotAllowedCount; i++) { - final OpToChange op = mOpsToIgnoreIfNotAllowed.get(i); + final OpToChange op = mOpsToIgnoreIfNotAllowed.valueAt(i); if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) { continue; } - boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid, op.packageName); + boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid); if (wasSet) { alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1); } @@ -555,7 +555,7 @@ public final class PermissionPolicyService extends SystemService { } int uid = packageInfo.applicationInfo.uid; - OpToChange opToChange = new OpToChange(uid, packageName, appOpCode); + OpToChange opToChange = new OpToChange(uid, appOpCode); switch (appOpMode) { case MODE_ALLOWED: mOpsToAllow.add(opToChange); @@ -618,8 +618,7 @@ public final class PermissionPolicyService extends SystemService { } int uid = packageInfo.applicationInfo.uid; - String packageName = packageInfo.packageName; - OpToChange extraOpToChange = new OpToChange(uid, packageName, extraOpCode); + OpToChange extraOpToChange = new OpToChange(uid, extraOpCode); if (policy.mayAllowExtraAppOp()) { mOpsToAllow.add(extraOpToChange); } else { @@ -632,45 +631,56 @@ public final class PermissionPolicyService extends SystemService { } /** - * Add a package for {@link #syncPackages() processing} later. + * Add a Uid for {@link #syncUids() processing} later. * * <p>Note: Called with the package lock held. Do <u>not</u> call into app-op manager. * - * @param pkgName The package to add for later processing. + * @param uid The uid to add for later processing. */ - void addPackage(@NonNull String pkgName) { - final PackageInfo pkg; - try { - pkg = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS); - } catch (NameNotFoundException e) { + void addUid(int uid) { + String[] pkgNames = getPackageNamesForUid(uid); + if (pkgNames == null) { return; } - if (pkg.requestedPermissions == null) { - return; - } + for (String pkgName : pkgNames) { + final PackageInfo pkg; + try { + pkg = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS); + } catch (NameNotFoundException e) { + continue; + } + + if (pkg.requestedPermissions == null) { + continue; + } - for (String permission : pkg.requestedPermissions) { - addAppOps(pkg, permission); + for (String permission : pkg.requestedPermissions) { + addAppOps(pkg, permission); + } } } - private void setUidModeAllowed(int opCode, int uid, @NonNull String packageName) { - setUidMode(opCode, uid, MODE_ALLOWED, packageName); + private void setUidModeAllowed(int opCode, int uid) { + setUidMode(opCode, uid, MODE_ALLOWED); } - private void setUidModeForeground(int opCode, int uid, @NonNull String packageName) { - setUidMode(opCode, uid, MODE_FOREGROUND, packageName); + private void setUidModeForeground(int opCode, int uid) { + setUidMode(opCode, uid, MODE_FOREGROUND); } - private void setUidModeIgnored(int opCode, int uid, @NonNull String packageName) { - setUidMode(opCode, uid, MODE_IGNORED, packageName); + private void setUidModeIgnored(int opCode, int uid) { + setUidMode(opCode, uid, MODE_IGNORED); } - private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid, - @NonNull String packageName) { + private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid) { + String[] pkgsOfUid = getPackageNamesForUid(uid); + if (ArrayUtils.isEmpty(pkgsOfUid)) { + return false; + } + final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName( - opCode), uid, packageName); + opCode), uid, pkgsOfUid[0]); if (currentMode != MODE_ALLOWED) { if (currentMode != MODE_IGNORED) { mAppOpsManagerInternal.setUidModeIgnoringCallback(opCode, uid, MODE_IGNORED, @@ -681,20 +691,24 @@ public final class PermissionPolicyService extends SystemService { return false; } - private void setUidMode(int opCode, int uid, int mode, - @NonNull String packageName) { + private void setUidMode(int opCode, int uid, int mode) { + String[] pkgsOfUid = getPackageNamesForUid(uid); + if (ArrayUtils.isEmpty(pkgsOfUid)) { + return; + } + final int oldMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName( - opCode), uid, packageName); + opCode), uid, pkgsOfUid[0]); if (oldMode != mode) { mAppOpsManagerInternal.setUidModeIgnoringCallback(opCode, uid, mode, mAppOpsCallback); final int newMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName( - opCode), uid, packageName); + opCode), uid, pkgsOfUid[0]); if (newMode != mode) { // Work around incorrectly-set package mode. It never makes sense for app ops // related to runtime permissions, but can get in the way and we have to reset // it. - mAppOpsManagerInternal.setModeIgnoringCallback(opCode, uid, packageName, + mAppOpsManagerInternal.setModeIgnoringCallback(opCode, uid, pkgsOfUid[0], AppOpsManager.opToDefaultMode(opCode), mAppOpsCallback); } } @@ -702,14 +716,30 @@ public final class PermissionPolicyService extends SystemService { private class OpToChange { final int uid; - final @NonNull String packageName; final int code; - OpToChange(int uid, @NonNull String packageName, int code) { + OpToChange(int uid, int code) { this.uid = uid; - this.packageName = packageName; this.code = code; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + OpToChange other = (OpToChange) o; + return uid == other.uid && code == other.code; + } + + @Override + public int hashCode() { + return Objects.hash(uid, code); + } } } diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java index 81ec46634e8a..a83c58d9c5f4 100644 --- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java +++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java @@ -27,16 +27,22 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYST import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static java.lang.Integer.min; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.os.Build; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; +import android.util.Log; + +import com.android.internal.compat.IPlatformCompat; /** * The behavior of soft restricted permissions is different for each permission. This class collects @@ -46,6 +52,27 @@ import android.os.UserHandle; * {@link com.android.packageinstaller.permission.utils.SoftRestrictedPermissionPolicy} */ public abstract class SoftRestrictedPermissionPolicy { + /** + * Enables scoped storage, with exceptions for apps that explicitly request legacy access, or + * apps that hold the {@code android.Manifest.permission#WRITE_MEDIA_STORAGE} permission. + * See https://developer.android.com/training/data-storage#scoped-storage for more information. + */ + @ChangeId + // This change is enabled for apps with targetSDK > {@link android.os.Build.VERSION_CODES.P} + @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.P) + static final long ENABLE_SCOPED_STORAGE = 144914977L; + + /** + * Enforces scoped storage for all apps, preventing individual apps from opting out. This change + * has precedence over {@code ENABLE_SCOPED_STORAGE}. + */ + @ChangeId + // This change is enabled for apps with targetSDK > {@link android.os.Build.VERSION_CODES.Q}. + @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.Q) + static final long REQUIRE_SCOPED_STORAGE = 131432978L; + + private static final String LOG_TAG = SoftRestrictedPermissionPolicy.class.getSimpleName(); + private static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT @@ -60,41 +87,6 @@ public abstract class SoftRestrictedPermissionPolicy { }; /** - * TargetSDK is per package. To make sure two apps int the same shared UID do not fight over - * what to set, always compute the combined targetSDK. - * - * @param context A context - * @param appInfo The app that is changed - * @param user The user the app belongs to - * - * @return The minimum targetSDK of all apps sharing the uid of the app - */ - private static int getMinimumTargetSDK(@NonNull Context context, - @NonNull ApplicationInfo appInfo, @NonNull UserHandle user) { - PackageManager pm = context.getPackageManager(); - - int minimumTargetSDK = appInfo.targetSdkVersion; - - String[] uidPkgs = pm.getPackagesForUid(appInfo.uid); - if (uidPkgs != null) { - for (String uidPkg : uidPkgs) { - if (!uidPkg.equals(appInfo.packageName)) { - ApplicationInfo uidPkgInfo; - try { - uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user); - } catch (PackageManager.NameNotFoundException e) { - continue; - } - - minimumTargetSDK = min(minimumTargetSDK, uidPkgInfo.targetSdkVersion); - } - } - } - - return minimumTargetSDK; - } - - /** * Get the policy for a soft restricted permission. * * @param context A context to use @@ -114,26 +106,31 @@ public abstract class SoftRestrictedPermissionPolicy { case READ_EXTERNAL_STORAGE: { final boolean isWhiteListed; boolean shouldApplyRestriction; - final int targetSDK; final boolean hasRequestedLegacyExternalStorage; final boolean hasWriteMediaStorageGrantedForUid; + final boolean isScopedStorageEnabled; if (appInfo != null) { PackageManager pm = context.getPackageManager(); int flags = pm.getPermissionFlags(permission, appInfo.packageName, user); isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; - shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0; - targetSDK = getMinimumTargetSDK(context, appInfo, user); hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage( appInfo.uid, context); hasWriteMediaStorageGrantedForUid = hasWriteMediaStorageGrantedForUid( appInfo.uid, context); + final boolean isScopedStorageRequired = + isChangeEnabledForUid(context, appInfo, user, REQUIRE_SCOPED_STORAGE); + isScopedStorageEnabled = + isChangeEnabledForUid(context, appInfo, user, ENABLE_SCOPED_STORAGE) + || isScopedStorageRequired; + shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0 + || isScopedStorageRequired; } else { isWhiteListed = false; shouldApplyRestriction = false; - targetSDK = 0; hasRequestedLegacyExternalStorage = false; hasWriteMediaStorageGrantedForUid = false; + isScopedStorageEnabled = false; } // We have a check in PermissionPolicyService.PermissionToOpSynchroniser.setUidMode @@ -143,7 +140,7 @@ public abstract class SoftRestrictedPermissionPolicy { return new SoftRestrictedPermissionPolicy() { @Override public boolean mayGrantPermission() { - return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q; + return isWhiteListed || isScopedStorageEnabled; } @Override public int getExtraAppOpCode() { @@ -151,7 +148,7 @@ public abstract class SoftRestrictedPermissionPolicy { } @Override public boolean mayAllowExtraAppOp() { - return !shouldApplyRestriction && targetSDK <= Build.VERSION_CODES.Q + return !shouldApplyRestriction && (hasRequestedLegacyExternalStorage || hasWriteMediaStorageGrantedForUid); } @@ -163,22 +160,26 @@ public abstract class SoftRestrictedPermissionPolicy { } case WRITE_EXTERNAL_STORAGE: { final boolean isWhiteListed; - final int targetSDK; + final boolean isScopedStorageEnabled; if (appInfo != null) { final int flags = context.getPackageManager().getPermissionFlags(permission, appInfo.packageName, user); isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; - targetSDK = getMinimumTargetSDK(context, appInfo, user); + final boolean isScopedStorageRequired = + isChangeEnabledForUid(context, appInfo, user, REQUIRE_SCOPED_STORAGE); + isScopedStorageEnabled = + isChangeEnabledForUid(context, appInfo, user, ENABLE_SCOPED_STORAGE) + || isScopedStorageRequired; } else { isWhiteListed = false; - targetSDK = 0; + isScopedStorageEnabled = false; } return new SoftRestrictedPermissionPolicy() { @Override public boolean mayGrantPermission() { - return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q; + return isWhiteListed || isScopedStorageEnabled; } }; } @@ -187,6 +188,62 @@ public abstract class SoftRestrictedPermissionPolicy { } } + /** + * Checks whether an AppCompat change is enabled for all packages sharing a UID with the + * provided application. + * + * @param context A context to use. + * @param appInfo The application for which to check whether the compat change is enabled. + * @param user The user the app belongs to. + * @param changeId A {@link android.compat.annotation.ChangeId} corresponding to the change. + * + * @return true if this change is enabled for all apps sharing the UID of the provided app, + * false otherwise. + */ + private static boolean isChangeEnabledForUid(@NonNull Context context, + @NonNull ApplicationInfo appInfo, @NonNull UserHandle user, long changeId) { + PackageManager pm = context.getPackageManager(); + + String[] uidPackages = pm.getPackagesForUid(appInfo.uid); + if (uidPackages != null) { + for (String uidPackage : uidPackages) { + ApplicationInfo uidPackageInfo; + try { + uidPackageInfo = pm.getApplicationInfoAsUser(uidPackage, 0, user); + } catch (PackageManager.NameNotFoundException e) { + continue; + } + if (!isChangeEnabled(uidPackageInfo, changeId)) { + // At least one package sharing this UID does not have this change enabled. + return false; + } + } + // All packages sharing this UID returned true for {@link #isChangeEnabled()}. + return true; + } else { + Log.w(LOG_TAG, "Check for change " + changeId + " for uid " + appInfo.uid + + " produced no packages. Defaulting to using the information for " + + appInfo.packageName + " only."); + return isChangeEnabled(appInfo, changeId); + } + } + + private static boolean isChangeEnabled(@NonNull ApplicationInfo appInfo, long changeId) { + IBinder binder = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(binder); + + final long callingId = Binder.clearCallingIdentity(); + + try { + return platformCompat.isChangeEnabled(changeId, appInfo); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Check for change " + changeId + " failed. Defaulting to enabled.", e); + return true; + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + private static boolean hasUidRequestedLegacyExternalStorage(int uid, @NonNull Context context) { PackageManager packageManager = context.getPackageManager(); String[] packageNames = packageManager.getPackagesForUid(uid); diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java index e77839c4e5b5..524ae54972e5 100644 --- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java +++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java @@ -224,6 +224,13 @@ public class AppDataRollbackHelper { } /** + * Deletes all device-encrypted apex data snapshots for the given rollback id. + */ + public void destroyApexDeSnapshots(int rollbackId) { + mApexManager.destroyDeSnapshots(rollbackId); + } + + /** * Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If * the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode * of the CE user data snapshot. diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index b5da1c2ec58a..5abd9f0698d3 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -44,7 +44,6 @@ import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; -import com.android.server.pm.ApexManager; import java.io.File; import java.io.IOException; @@ -671,7 +670,7 @@ class Rollback { } } if (containsApex) { - ApexManager.getInstance().destroyDeSnapshots(info.getRollbackId()); + dataHelper.destroyApexDeSnapshots(info.getRollbackId()); } RollbackStore.deleteRollback(this); diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java index f3f14a95eac6..1be6f227b44b 100644 --- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java +++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java @@ -38,9 +38,9 @@ import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; import android.util.ArraySet; import android.util.Slog; -import android.util.StatsLog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.PackageWatchdog; import java.util.ArrayList; @@ -206,12 +206,25 @@ public final class WatchdogRollbackLogger { + " rollbackReason: " + rollbackReasonToString(rollbackReason) + " failedPackageName: " + failingPackageName); if (logPackage != null) { - StatsLog.logWatchdogRollbackOccurred(type, logPackage.getPackageName(), - logPackage.getVersionCode(), rollbackReason, failingPackageName); + FrameworkStatsLog.write( + FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED, + type, + logPackage.getPackageName(), + logPackage.getVersionCode(), + rollbackReason, + failingPackageName, + new byte[]{}); } else { // In the case that the log package is null, still log an empty string as an // indication that retrieving the logging parent failed. - StatsLog.logWatchdogRollbackOccurred(type, "", 0, rollbackReason, failingPackageName); + FrameworkStatsLog.write( + FrameworkStatsLog.WATCHDOG_ROLLBACK_OCCURRED, + type, + "", + 0, + rollbackReason, + failingPackageName, + new byte[]{}); } } diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 34d2c16ed0ac..58455ca2753b 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -36,7 +36,7 @@ import android.service.textclassifier.ITextClassifierService; import android.service.textclassifier.TextClassifierService; import android.service.textclassifier.TextClassifierService.ConnectionState; import android.text.TextUtils; -import android.util.LruCache; +import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.view.textclassifier.ConversationActions; @@ -65,6 +65,7 @@ import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Queue; @@ -146,11 +147,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi private final Object mLock; @GuardedBy("mLock") final SparseArray<UserState> mUserStates = new SparseArray<>(); - // SystemTextClassifier.onDestroy() is not guaranteed to be called, use LruCache here - // to avoid leak. - @GuardedBy("mLock") - private final LruCache<TextClassificationSessionId, TextClassificationContext> - mSessionContextCache = new LruCache<>(40); + private final SessionCache mSessionCache; private final TextClassificationConstants mSettings; @Nullable private final String mDefaultTextClassifierPackage; @@ -165,6 +162,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi PackageManager packageManager = mContext.getPackageManager(); mDefaultTextClassifierPackage = packageManager.getDefaultTextClassifierPackageName(); mSystemTextClassifierPackage = packageManager.getSystemTextClassifierPackageName(); + mSessionCache = new SessionCache(mLock); } private void startListenSettings() { @@ -314,7 +312,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi classificationContext.getUseDefaultTextClassifier(), service -> { service.onCreateTextClassificationSession(classificationContext, sessionId); - mSessionContextCache.put(sessionId, classificationContext); + mSessionCache.put(sessionId, classificationContext); }, "onCreateTextClassificationSession", NO_OP_CALLBACK); @@ -326,14 +324,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi Objects.requireNonNull(sessionId); synchronized (mLock) { - TextClassificationContext textClassificationContext = - mSessionContextCache.get(sessionId); + final StrippedTextClassificationContext textClassificationContext = + mSessionCache.get(sessionId); final int userId = textClassificationContext != null - ? textClassificationContext.getUserId() + ? textClassificationContext.userId : UserHandle.getCallingUserId(); final boolean useDefaultTextClassifier = textClassificationContext != null - ? textClassificationContext.getUseDefaultTextClassifier() + ? textClassificationContext.useDefaultTextClassifier : true; handleRequest( userId, @@ -342,7 +340,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi useDefaultTextClassifier, service -> { service.onDestroyTextClassificationSession(sessionId); - mSessionContextCache.remove(sessionId); + mSessionCache.remove(sessionId); }, "onDestroyTextClassificationSession", NO_OP_CALLBACK); @@ -409,7 +407,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi pw.decreaseIndent(); } } - pw.println("Number of active sessions: " + mSessionContextCache.size()); + pw.println("Number of active sessions: " + mSessionCache.size()); } } @@ -568,6 +566,81 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } + /** + * Stores the stripped down version of {@link TextClassificationContext}s, i.e. {@link + * StrippedTextClassificationContext}, keyed by {@link TextClassificationSessionId}. Sessions + * are cleaned up automatically when the client process is dead. + */ + static final class SessionCache { + @NonNull + private final Object mLock; + @NonNull + @GuardedBy("mLock") + private final Map<TextClassificationSessionId, StrippedTextClassificationContext> mCache = + new ArrayMap<>(); + @NonNull + @GuardedBy("mLock") + private final Map<TextClassificationSessionId, DeathRecipient> mDeathRecipients = + new ArrayMap<>(); + + SessionCache(@NonNull Object lock) { + mLock = Objects.requireNonNull(lock); + } + + void put(@NonNull TextClassificationSessionId sessionId, + @NonNull TextClassificationContext textClassificationContext) { + synchronized (mLock) { + mCache.put(sessionId, + new StrippedTextClassificationContext(textClassificationContext)); + try { + DeathRecipient deathRecipient = () -> remove(sessionId); + sessionId.getToken().linkToDeath(deathRecipient, /* flags= */ 0); + mDeathRecipients.put(sessionId, deathRecipient); + } catch (RemoteException e) { + Slog.w(LOG_TAG, "SessionCache: Failed to link to death", e); + } + } + } + + @Nullable + StrippedTextClassificationContext get(@NonNull TextClassificationSessionId sessionId) { + Objects.requireNonNull(sessionId); + synchronized (mLock) { + return mCache.get(sessionId); + } + } + + void remove(@NonNull TextClassificationSessionId sessionId) { + Objects.requireNonNull(sessionId); + synchronized (mLock) { + DeathRecipient deathRecipient = mDeathRecipients.get(sessionId); + if (deathRecipient != null) { + sessionId.getToken().unlinkToDeath(deathRecipient, /* flags= */ 0); + } + mDeathRecipients.remove(sessionId); + mCache.remove(sessionId); + } + } + + int size() { + synchronized (mLock) { + return mCache.size(); + } + } + } + + /** A stripped down version of {@link TextClassificationContext}. */ + static class StrippedTextClassificationContext { + @UserIdInt + public final int userId; + public final boolean useDefaultTextClassifier; + + StrippedTextClassificationContext(TextClassificationContext textClassificationContext) { + userId = textClassificationContext.getUserId(); + useDefaultTextClassifier = textClassificationContext.getUseDefaultTextClassifier(); + } + } + private final class UserState { @UserIdInt final int mUserId; diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index 8c54fa95e04b..88de34027bb9 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -87,11 +87,15 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000; /** - * The number of previous telephony suggestions to keep for each ID (for use during debugging). + * The number of suggestions to keep. These are logged in bug reports to assist when debugging + * issues with detection. */ - private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30; + private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10; - // A log for changes made to the system clock and why. + /** + * A log that records the decisions / decision metadata that affected the device's system clock + * time. This is logged in bug reports to assist with debugging issues with detection. + */ @NonNull private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */); @@ -140,7 +144,18 @@ public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { if (!validateSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) { return; } - mLastNetworkSuggestion.set(timeSuggestion); + + // The caller submits suggestions with the best available information when there are network + // changes. The best available information may have been cached and if they were all stored + // this would lead to duplicates showing up in the suggestion history. The suggestions may + // be made for different reasons but there is not a significant benefit to storing the same + // suggestion information again. doAutoTimeDetection() should still be called: this ensures + // the suggestion and device state are always re-evaluated, which might produce a different + // detected time if, for example, the age of all suggestions are considered. + NetworkTimeSuggestion lastNetworkSuggestion = mLastNetworkSuggestion.get(); + if (lastNetworkSuggestion == null || !lastNetworkSuggestion.equals(timeSuggestion)) { + mLastNetworkSuggestion.set(timeSuggestion); + } // Now perform auto time detection. The new suggestion may be used to modify the system // clock. diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index 652dbe153680..cc33fb0f5008 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -161,16 +161,17 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat public static final int TELEPHONY_SCORE_USAGE_THRESHOLD = TELEPHONY_SCORE_MEDIUM; /** - * The number of previous telephony suggestions to keep for each ID (for use during debugging). + * The number of suggestions to keep. These are logged in bug reports to assist when debugging + * issues with detection. */ - private static final int KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE = 30; + private static final int KEEP_SUGGESTION_HISTORY_SIZE = 10; @NonNull private final Callback mCallback; /** - * A log that records the decisions / decision metadata that affected the device's time zone - * (for use during debugging). + * A log that records the decisions / decision metadata that affected the device's time zone. + * This is logged in bug reports to assist with debugging issues with detection. */ @NonNull private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */); @@ -182,8 +183,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat */ @GuardedBy("this") private ArrayMapWithHistory<Integer, QualifiedTelephonyTimeZoneSuggestion> - mSuggestionBySlotIndex = - new ArrayMapWithHistory<>(KEEP_TELEPHONY_SUGGESTION_HISTORY_SIZE); + mSuggestionBySlotIndex = new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE); /** * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}. diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9b968cb6317a..aa6d8545f705 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7674,7 +7674,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mainWindow.getContentInsets(insets); InsetUtils.addInsets(insets, getLetterboxInsets()); return new RemoteAnimationTarget(task.mTaskId, record.getMode(), - record.mAdapter.mCapturedLeash, !task.fillsParent(), + record.mAdapter.mCapturedLeash, !fillsParent(), mainWindow.mWinAnimator.mLastClipRect, insets, getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mStackBounds, task.getWindowConfiguration(), diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 80162fbe5e97..4b96ea0a85dd 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2795,6 +2795,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + // TODO(148895075): deprecate and replace with task equivalents @Override public List<ActivityManager.StackInfo> getAllStackInfos() { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()"); @@ -2821,6 +2822,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + // TODO(148895075): deprecate and replace with task equivalents @Override public List<ActivityManager.StackInfo> getAllStackInfosOnDisplay(int displayId) { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()"); diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 06e7b48a1f04..9e93e1455f2c 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -18,6 +18,9 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import android.content.res.Resources; +import android.text.TextUtils; + import com.android.server.wm.DisplayContent.TaskContainers; /** @@ -42,7 +45,18 @@ public abstract class DisplayAreaPolicy { */ protected final TaskContainers mTaskContainers; - DisplayAreaPolicy(WindowManagerService wmService, + /** + * Construct a new {@link DisplayAreaPolicy} + * + * @param wmService the window manager service instance + * @param content the display content for which the policy applies + * @param root the root display area under which the policy operates + * @param imeContainer the ime container that the policy must attach + * @param taskContainers the task container that the policy must attach + * + * @see #attachDisplayAreas() + */ + protected DisplayAreaPolicy(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) { mWmService = wmService; @@ -119,5 +133,55 @@ public abstract class DisplayAreaPolicy { throw new IllegalArgumentException("don't know how to sort " + token); } } + + /** Provider for {@link DisplayAreaPolicy.Default platform-default display area policy}. */ + static class Provider implements DisplayAreaPolicy.Provider { + @Override + public DisplayAreaPolicy instantiate(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + TaskContainers taskContainers) { + return new DisplayAreaPolicy.Default(wmService, content, root, imeContainer, + taskContainers); + } + } + } + + /** + * Provider for {@link DisplayAreaPolicy} instances. + * + * By implementing this interface and overriding the + * {@code config_deviceSpecificDisplayAreaPolicyProvider}, a device-specific implementations + * of {@link DisplayAreaPolicy} can be supplied. + */ + public interface Provider { + /** + * Instantiate a new DisplayAreaPolicy. + * + * @see DisplayAreaPolicy#DisplayAreaPolicy + */ + DisplayAreaPolicy instantiate(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + TaskContainers taskContainers); + + /** + * Instantiate the device-specific {@link Provider}. + */ + static Provider fromResources(Resources res) { + String name = res.getString( + com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider); + if (TextUtils.isEmpty(name)) { + return new DisplayAreaPolicy.Default.Provider(); + } + try { + return (Provider) Class.forName(name).newInstance(); + } catch (ReflectiveOperationException | ClassCastException e) { + throw new IllegalStateException("Couldn't instantiate class " + name + + " for config_deviceSpecificDisplayAreaPolicyProvider:" + + " make sure it has a public zero-argument constructor" + + " and implements DisplayAreaPolicy.Provider", e); + } + } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4f1df7da27f1..3b658c04b8c2 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -304,8 +304,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final DisplayArea.Root mRootDisplayArea = new DisplayArea.Root(mWmService); - private final DisplayAreaPolicy mDisplayAreaPolicy = new DisplayAreaPolicy.Default( - mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers); + private final DisplayAreaPolicy mDisplayAreaPolicy; private WindowState mTmpWindow; private WindowState mTmpWindow2; @@ -1027,6 +1026,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo super.addChild(mWindowContainers, null); super.addChild(mOverlayContainers, null); + mDisplayAreaPolicy = mWmService.mDisplayAreaPolicyProvider.instantiate( + mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers); mWindowContainers.addChildren(); // Sets the display content for the children. diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index d0179adadbd7..51b9916159fe 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -44,6 +44,7 @@ import android.view.ViewRootImpl; import android.view.WindowInsetsAnimationCallback; import android.view.WindowInsetsAnimationControlListener; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.DisplayThread; /** @@ -107,11 +108,11 @@ class InsetsPolicy { changed = true; } if (changed) { - startAnimation(mShowingTransientTypes, true, () -> { + mPolicy.getStatusBarManagerInternal().showTransient(mDisplayContent.getDisplayId(), + mShowingTransientTypes.toArray()); + updateBarControlTarget(mFocusedWin); + startAnimation(true /* show */, () -> { synchronized (mDisplayContent.mWmService.mGlobalLock) { - mPolicy.getStatusBarManagerInternal().showTransient( - mDisplayContent.getDisplayId(), - mShowingTransientTypes.toArray()); mStateController.notifyInsetsChanged(); } }); @@ -122,7 +123,7 @@ class InsetsPolicy { if (mShowingTransientTypes.size() == 0) { return; } - startAnimation(mShowingTransientTypes, false, () -> { + startAnimation(false /* show */, () -> { synchronized (mDisplayContent.mWmService.mGlobalLock) { mShowingTransientTypes.clear(); mStateController.notifyInsetsChanged(); @@ -268,18 +269,20 @@ class InsetsPolicy { return isDockedStackVisible || isFreeformStackVisible || isResizing; } - private void startAnimation(IntArray internalTypes, boolean show, Runnable callback) { + @VisibleForTesting + void startAnimation(boolean show, Runnable callback) { int typesReady = 0; final SparseArray<InsetsSourceControl> controls = new SparseArray<>(); - updateBarControlTarget(mFocusedWin); - for (int i = internalTypes.size() - 1; i >= 0; i--) { + final IntArray showingTransientTypes = mShowingTransientTypes; + for (int i = showingTransientTypes.size() - 1; i >= 0; i--) { InsetsSourceProvider provider = - mStateController.getSourceProvider(internalTypes.get(i)); - if (provider == null) continue; - InsetsSourceControl control = provider.getControl(provider.getControlTarget()); - if (control == null || control.getLeash() == null) continue; - typesReady |= InsetsState.toPublicType(internalTypes.get(i)); - controls.put(control.getType(), control); + mStateController.getSourceProvider(showingTransientTypes.get(i)); + InsetsSourceControl control = provider.getControl(mTransientControlTarget); + if (control == null || control.getLeash() == null) { + continue; + } + typesReady |= InsetsState.toPublicType(showingTransientTypes.get(i)); + controls.put(control.getType(), new InsetsSourceControl(control)); } controlAnimationUnchecked(typesReady, controls, show, callback); } @@ -335,7 +338,6 @@ class InsetsPolicy { private InsetsPolicyAnimationControlListener mListener; InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) { - super(); mListener = listener; } @@ -353,9 +355,11 @@ class InsetsPolicy { InsetsController.INTERPOLATOR, true, show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); + SurfaceAnimationThread.getHandler().post( + () -> mListener.onReady(mAnimationControl, typesReady)); } - /** Called on SurfaceAnimationThread lock without global WM lock held. */ + /** Called on SurfaceAnimationThread without global WM lock held. */ @Override public void scheduleApplyChangeInsets() { InsetsState state = getState(); @@ -384,7 +388,7 @@ class InsetsPolicy { return overrideState; } - /** Called on SurfaceAnimationThread lock without global WM lock held. */ + /** Called on SurfaceAnimationThread without global WM lock held. */ @Override public void applySurfaceParams( final SyncRtSurfaceTransactionApplier.SurfaceParams... params) { @@ -396,14 +400,12 @@ class InsetsPolicy { t.apply(); } - /** Called on SurfaceAnimationThread lock without global WM lock held. */ @Override public void startAnimation(InsetsAnimationControlImpl controller, WindowInsetsAnimationControlListener listener, int types, WindowInsetsAnimationCallback.InsetsAnimation animation, WindowInsetsAnimationCallback.AnimationBounds bounds, int layoutDuringAnimation) { - SurfaceAnimationThread.getHandler().post(() -> listener.onReady(controller, types)); } } } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 798665972a33..0d3f6b98f483 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -59,6 +59,7 @@ class InsetsSourceProvider { private final InsetsSourceControl mFakeControl; private @Nullable InsetsSourceControl mControl; private @Nullable InsetsControlTarget mControlTarget; + private @Nullable InsetsControlTarget mPendingControlTarget; private @Nullable InsetsControlTarget mFakeControlTarget; private @Nullable ControlAdapter mAdapter; @@ -140,8 +141,9 @@ class InsetsSourceProvider { mSource.setVisibleFrame(null); } else if (mControllable) { mWin.setControllableInsetProvider(this); - if (mControlTarget != null) { - updateControlForTarget(mControlTarget, true /* force */); + if (mPendingControlTarget != null) { + updateControlForTarget(mPendingControlTarget, true /* force */); + mPendingControlTarget = null; } } } @@ -245,7 +247,7 @@ class InsetsSourceProvider { setWindow(null, null, null); } if (mWin == null) { - mControlTarget = target; + mPendingControlTarget = target; return; } if (target == mControlTarget && !force) { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index caaa4305406c..2d7d3f18c101 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -89,6 +90,12 @@ class InsetsStateController { if (type == ITYPE_NAVIGATION_BAR) { state.removeSource(ITYPE_IME); state.removeSource(ITYPE_STATUS_BAR); + state.removeSource(ITYPE_CAPTION_BAR); + } + + // Status bar doesn't get influenced by caption bar + if (type == ITYPE_STATUS_BAR) { + state.removeSource(ITYPE_CAPTION_BAR); } // IME needs different frames for certain cases (e.g. navigation bar in gesture nav). @@ -212,18 +219,18 @@ class InsetsStateController { /** * Called when the focused window that is able to control the system bars changes. * - * @param topControlling The target that is now able to control the top bar appearance - * and visibility. + * @param statusControlling The target that is now able to control the status bar appearance + * and visibility. * @param navControlling The target that is now able to control the nav bar appearance * and visibility. */ - void onBarControlTargetChanged(@Nullable InsetsControlTarget topControlling, - @Nullable InsetsControlTarget fakeTopControlling, + void onBarControlTargetChanged(@Nullable InsetsControlTarget statusControlling, + @Nullable InsetsControlTarget fakeStatusControlling, @Nullable InsetsControlTarget navControlling, @Nullable InsetsControlTarget fakeNavControlling) { - onControlChanged(ITYPE_STATUS_BAR, topControlling); + onControlChanged(ITYPE_STATUS_BAR, statusControlling); onControlChanged(ITYPE_NAVIGATION_BAR, navControlling); - onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeTopControlling); + onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling); onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling); notifyPendingInsetsControlChanged(); } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2fdcbc9083f6..2196d899406d 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3323,6 +3323,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> * @return {@code true} if the top activity looks like it belongs to {@param userId}. */ private void taskTopActivityIsUser(Task task, @UserIdInt int userId) { + // TODO(b/80414790): having utilities to loop for all leaf tasks from caller vs. checking + // leaf tasks here. + if (!task.isLeafTask()) { + // No op if not a leaf task since we don't want to report root tasks to + // TaskStackListeners. + return; + } + // To handle the case that work app is in the task but just is not the top one. final ActivityRecord activityRecord = task.getTopNonFinishingActivity(); final ActivityRecord resultTo = (activityRecord != null ? activityRecord.resultTo : null); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 028274a5641c..326335e6df46 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1433,18 +1433,27 @@ class Task extends WindowContainer<WindowContainer> { } /** - * @return whether or not there are ONLY task overlay activities in the stack. + * @return whether or not there are ONLY task overlay activities in the task. * If {@param includeFinishing} is set, then don't ignore finishing activities in the * check. If there are no task overlay activities, this call returns false. */ boolean onlyHasTaskOverlayActivities(boolean includeFinishing) { - if (getChildCount() == 0) { - return false; - } - if (includeFinishing) { - return getActivity((r) -> r.isTaskOverlay()) != null; + int count = 0; + for (int i = getChildCount() - 1; i >= 0; i--) { + final ActivityRecord r = getChildAt(i).asActivityRecord(); + if (r == null) { + // Has a child that is other than Activity. + return false; + } + if (!includeFinishing && r.finishing) { + continue; + } + if (!r.isTaskOverlay()) { + return false; + } + count++; } - return getActivity((r) -> !r.finishing && r.isTaskOverlay()) != null; + return count > 0; } private boolean autoRemoveFromRecents() { @@ -2371,6 +2380,15 @@ class Task extends WindowContainer<WindowContainer> { return getRootTask() == this; } + boolean isLeafTask() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + if (mChildren.get(i).asTask() != null) { + return false; + } + } + return true; + } + int getDescendantTaskCount() { final int[] currentCount = {0}; final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; }, diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 0a0530c92a16..4b13a0c1f75d 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -43,6 +43,7 @@ import android.view.IWindowContainer; import android.view.SurfaceControl; import android.view.WindowContainerTransaction; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledLambda; @@ -379,7 +380,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub } @Override - public List<RunningTaskInfo> getChildTasks(IWindowContainer parent) { + public List<RunningTaskInfo> getChildTasks(IWindowContainer parent, + @Nullable int[] activityTypes) { enforceStackPermission("getChildTasks()"); final long ident = Binder.clearCallingIdentity(); try { @@ -405,6 +407,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub for (int i = dc.getStackCount() - 1; i >= 0; --i) { final ActivityStack as = dc.getStackAt(i); if (as.getTile() == container) { + if (activityTypes != null + && !ArrayUtils.contains(activityTypes, as.getActivityType())) { + continue; + } final RunningTaskInfo info = new RunningTaskInfo(); as.fillTaskInfo(info); out.add(info); @@ -417,6 +423,40 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub } } + @Override + public List<RunningTaskInfo> getRootTasks(int displayId, @Nullable int[] activityTypes) { + enforceStackPermission("getRootTasks()"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final DisplayContent dc = + mService.mRootWindowContainer.getDisplayContent(displayId); + if (dc == null) { + throw new IllegalArgumentException("Display " + displayId + " doesn't exist"); + } + ArrayList<RunningTaskInfo> out = new ArrayList<>(); + for (int i = dc.getStackCount() - 1; i >= 0; --i) { + final ActivityStack task = dc.getStackAt(i); + if (task.getTile() != null) { + // a tile is supposed to look like a parent, so don't include their + // "children" here. They can be accessed via getChildTasks() + continue; + } + if (activityTypes != null + && !ArrayUtils.contains(activityTypes, task.getActivityType())) { + continue; + } + final RunningTaskInfo info = new RunningTaskInfo(); + task.fillTaskInfo(info); + out.add(info); + } + return out; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private int sanitizeAndApplyChange(WindowContainer container, WindowContainerTransaction.Change change) { if (!(container instanceof Task)) { diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index 8bbb0d7da4b6..b5892b9b4dc6 100644 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -20,6 +20,11 @@ import static android.app.ActivityTaskManager.RESIZE_MODE_USER; import static android.app.ActivityTaskManager.RESIZE_MODE_USER_FORCED; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING; @@ -29,7 +34,6 @@ import static com.android.server.wm.WindowManagerService.dipToPixel; import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; -import android.annotation.IntDef; import android.annotation.NonNull; import android.app.IActivityTaskManager; import android.graphics.Point; @@ -55,11 +59,10 @@ import android.view.SurfaceControl; import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.policy.TaskResizingAlgorithm; +import com.android.internal.policy.TaskResizingAlgorithm.CtrlType; import com.android.server.protolog.common.ProtoLog; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - class TaskPositioner implements IBinder.DeathRecipient { private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false; private static final String TAG_LOCAL = "TaskPositioner"; @@ -67,33 +70,10 @@ class TaskPositioner implements IBinder.DeathRecipient { private static Factory sFactory; - @IntDef(flag = true, - value = { - CTRL_NONE, - CTRL_LEFT, - CTRL_RIGHT, - CTRL_TOP, - CTRL_BOTTOM - }) - @Retention(RetentionPolicy.SOURCE) - @interface CtrlType {} - - private static final int CTRL_NONE = 0x0; - private static final int CTRL_LEFT = 0x1; - private static final int CTRL_RIGHT = 0x2; - private static final int CTRL_TOP = 0x4; - private static final int CTRL_BOTTOM = 0x8; - public static final float RESIZING_HINT_ALPHA = 0.5f; public static final int RESIZING_HINT_DURATION_MS = 0; - // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait). - // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever - // aspect he desires. - @VisibleForTesting - static final float MIN_ASPECT = 1.2f; - private final WindowManagerService mService; private final IActivityTaskManager mActivityManager; private WindowPositionerEventReceiver mInputEventReceiver; @@ -477,122 +457,13 @@ class TaskPositioner implements IBinder.DeathRecipient { */ @VisibleForTesting void resizeDrag(float x, float y) { - // This is a resizing operation. - // We need to keep various constraints: - // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y] - // 2. The orientation is kept - if required. - final int deltaX = Math.round(x - mStartDragX); - final int deltaY = Math.round(y - mStartDragY); - int left = mWindowOriginalBounds.left; - int top = mWindowOriginalBounds.top; - int right = mWindowOriginalBounds.right; - int bottom = mWindowOriginalBounds.bottom; - - // Calculate the resulting width and height of the drag operation. - int width = right - left; - int height = bottom - top; - if ((mCtrlType & CTRL_LEFT) != 0) { - width = Math.max(mMinVisibleWidth, width - deltaX); - } else if ((mCtrlType & CTRL_RIGHT) != 0) { - width = Math.max(mMinVisibleWidth, width + deltaX); - } - if ((mCtrlType & CTRL_TOP) != 0) { - height = Math.max(mMinVisibleHeight, height - deltaY); - } else if ((mCtrlType & CTRL_BOTTOM) != 0) { - height = Math.max(mMinVisibleHeight, height + deltaY); - } - - // If we have to preserve the orientation - check that we are doing so. - final float aspect = (float) width / (float) height; - if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT) - || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) { - // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major - // drag axis. What ever is producing the bigger rectangle will be chosen. - int width1; - int width2; - int height1; - int height2; - if (mStartOrientationWasLandscape) { - // Assuming that the width is our target we calculate the height. - width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width)); - height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT)); - if (height1 < mMinVisibleHeight) { - // If the resulting height is too small we adjust to the minimal size. - height1 = mMinVisibleHeight; - width1 = Math.max(mMinVisibleWidth, - Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT))); - } - // Assuming that the height is our target we calculate the width. - height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height)); - width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT)); - if (width2 < mMinVisibleWidth) { - // If the resulting width is too small we adjust to the minimal size. - width2 = mMinVisibleWidth; - height2 = Math.max(mMinVisibleHeight, - Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT))); - } - } else { - // Assuming that the width is our target we calculate the height. - width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width)); - height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT)); - if (height1 < mMinVisibleHeight) { - // If the resulting height is too small we adjust to the minimal size. - height1 = mMinVisibleHeight; - width1 = Math.max(mMinVisibleWidth, - Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT))); - } - // Assuming that the height is our target we calculate the width. - height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height)); - width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT)); - if (width2 < mMinVisibleWidth) { - // If the resulting width is too small we adjust to the minimal size. - width2 = mMinVisibleWidth; - height2 = Math.max(mMinVisibleHeight, - Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT))); - } - } - - // Use the bigger of the two rectangles if the major change was positive, otherwise - // do the opposite. - final boolean grows = width > (right - left) || height > (bottom - top); - if (grows == (width1 * height1 > width2 * height2)) { - width = width1; - height = height1; - } else { - width = width2; - height = height2; - } - } - - // Update mWindowDragBounds to the new drag size. - updateDraggedBounds(left, top, right, bottom, width, height); + updateDraggedBounds(TaskResizingAlgorithm.resizeDrag(x, y, mStartDragX, mStartDragY, + mWindowOriginalBounds, mCtrlType, mMinVisibleWidth, mMinVisibleHeight, + mMaxVisibleSize, mPreserveOrientation, mStartOrientationWasLandscape)); } - /** - * Given the old coordinates and the new width and height, update the mWindowDragBounds. - * - * @param left The original left bound before the user started dragging. - * @param top The original top bound before the user started dragging. - * @param right The original right bound before the user started dragging. - * @param bottom The original bottom bound before the user started dragging. - * @param newWidth The new dragged width. - * @param newHeight The new dragged height. - */ - void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth, - int newHeight) { - // Generate the final bounds by keeping the opposite drag edge constant. - if ((mCtrlType & CTRL_LEFT) != 0) { - left = right - newWidth; - } else { // Note: The right might have changed - if we pulled at the right or not. - right = left + newWidth; - } - if ((mCtrlType & CTRL_TOP) != 0) { - top = bottom - newHeight; - } else { // Note: The height might have changed - if we pulled at the bottom or not. - bottom = top + newHeight; - } - - mWindowDragBounds.set(left, top, right, bottom); + private void updateDraggedBounds(Rect newBounds) { + mWindowDragBounds.set(newBounds); checkBoundsForOrientationViolations(mWindowDragBounds); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 633098566b1b..d98c18c077b8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -187,6 +187,7 @@ import android.os.SystemService; import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; +import android.provider.DeviceConfig; import android.provider.Settings; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; @@ -309,6 +310,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM; + private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter"; + static final int LAYOUT_REPEAT_THRESHOLD = 4; static final boolean PROFILE_ORIENTATION = false; @@ -413,6 +416,8 @@ public class WindowManagerService extends IWindowManager.Stub final WindowTracing mWindowTracing; + final DisplayAreaPolicy.Provider mDisplayAreaPolicyProvider; + final private KeyguardDisableHandler mKeyguardDisableHandler; // TODO: eventually unify all keyguard state in a common place instead of having it spread over // AM's KeyguardController and the policy's KeyguardServiceDelegate. @@ -623,6 +628,9 @@ public class WindowManagerService extends IWindowManager.Stub // The root of the device window hierarchy. RootWindowContainer mRoot; + // Whether the system should use BLAST for ViewRootImpl + final boolean mUseBLAST; + int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; Rect mDockedStackCreateBounds; @@ -1137,6 +1145,10 @@ public class WindowManagerService extends IWindowManager.Stub mAnimator = new WindowAnimator(this); mRoot = new RootWindowContainer(this); + mUseBLAST = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, + WM_USE_BLAST_ADAPTER_FLAG, false); + mWindowPlacerLocked = new WindowSurfacePlacer(this); mTaskSnapshotController = new TaskSnapshotController(this); @@ -1255,6 +1267,10 @@ public class WindowManagerService extends IWindowManager.Stub LocalServices.addService(WindowManagerInternal.class, new LocalService()); mEmbeddedWindowController = new EmbeddedWindowController(mGlobalLock); + + mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources( + mContext.getResources()); + setGlobalShadowSettings(); } @@ -5051,6 +5067,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public boolean useBLAST() { + return mUseBLAST; + } + + @Override public void getInitialDisplaySize(int displayId, Point size) { synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); @@ -8021,7 +8042,11 @@ public class WindowManagerService extends IWindowManager.Stub int displayId, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper displayCutout) { synchronized (mGlobalLock) { - final DisplayContent dc = mRoot.getDisplayContent(displayId); + final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId); + if (dc == null) { + throw new WindowManager.InvalidDisplayException("Display#" + displayId + + "could not be found!"); + } final WindowToken windowToken = dc.getWindowToken(attrs.token); final ActivityRecord activity; if (windowToken != null && windowToken.asActivityRecord() != null) { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 812bc438246f..49c7e0a3b242 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -13,7 +13,6 @@ cc_library_static { ], srcs: [ - ":graphicsstats_proto", ":lib_alarmManagerService_native", "BroadcastRadio/JavaRef.cpp", "BroadcastRadio/NativeCallbackThread.cpp", @@ -53,7 +52,6 @@ cc_library_static { "com_android_server_UsbHostManager.cpp", "com_android_server_VibratorService.cpp", "com_android_server_PersistentDataBlockService.cpp", - "com_android_server_GraphicsStatsService.cpp", "com_android_server_am_CachedAppOptimizer.cpp", "com_android_server_am_LowMemDetector.cpp", "com_android_server_incremental_IncrementalManagerService.cpp", @@ -107,8 +105,6 @@ cc_defaults { "libinputflinger", "libinputflinger_base", "libinputservice", - "libprotobuf-cpp-lite", - "libprotoutil", "libstatshidl", "libstatspull", "libstatssocket", diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp deleted file mode 100644 index aa7067ee7509..000000000000 --- a/services/core/jni/com_android_server_GraphicsStatsService.cpp +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "GraphicsStatsService" - -#include <jni.h> -#include <log/log.h> -#include <nativehelper/JNIHelp.h> -#include <nativehelper/ScopedPrimitiveArray.h> -#include <nativehelper/ScopedUtfChars.h> -#include <JankTracker.h> -#include <service/GraphicsStatsService.h> -#include <stats_pull_atom_callback.h> -#include <stats_event.h> -#include <statslog.h> -#include <google/protobuf/io/zero_copy_stream_impl_lite.h> -#include <android/util/ProtoOutputStream.h> -#include "android/graphics/Utils.h" -#include "core_jni_helpers.h" -#include "protos/graphicsstats.pb.h" -#include <cstring> -#include <memory> - -namespace android { - -using namespace android::uirenderer; - -static jint getAshmemSize(JNIEnv*, jobject) { - return sizeof(ProfileData); -} - -static jlong createDump(JNIEnv*, jobject, jint fd, jboolean isProto) { - GraphicsStatsService::Dump* dump = GraphicsStatsService::createDump(fd, isProto - ? GraphicsStatsService::DumpType::Protobuf : GraphicsStatsService::DumpType::Text); - return reinterpret_cast<jlong>(dump); -} - -static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage, - jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { - std::string path; - const ProfileData* data = nullptr; - LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null"); - ScopedByteArrayRO buffer{env}; - if (jdata != nullptr) { - buffer.reset(jdata); - LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), - "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData)); - data = reinterpret_cast<const ProfileData*>(buffer.get()); - } - if (jpath != nullptr) { - ScopedUtfChars pathChars(env, jpath); - LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); - path.assign(pathChars.c_str(), pathChars.size()); - } - ScopedUtfChars packageChars(env, jpackage); - LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars"); - GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); - LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer"); - - const std::string package(packageChars.c_str(), packageChars.size()); - GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data); -} - -static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) { - ScopedUtfChars pathChars(env, jpath); - LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); - const std::string path(pathChars.c_str(), pathChars.size()); - GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); - GraphicsStatsService::addToDump(dump, path); -} - -static void finishDump(JNIEnv*, jobject, jlong dumpPtr) { - GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); - GraphicsStatsService::finishDump(dump); -} - -static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) { - GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); - std::vector<uint8_t>* result = new std::vector<uint8_t>(); - GraphicsStatsService::finishDumpInMemory(dump, - [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) { - std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2); - if (outBuffer->size() < totalSize) { - outBuffer->resize(totalSize); - } - std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize); - }, env, result); - return reinterpret_cast<jlong>(result); -} - -static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, - jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { - ScopedByteArrayRO buffer(env, jdata); - LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), - "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData)); - ScopedUtfChars pathChars(env, jpath); - LOG_ALWAYS_FATAL_IF(pathChars.size() <= 0 || !pathChars.c_str(), "Failed to get path chars"); - ScopedUtfChars packageChars(env, jpackage); - LOG_ALWAYS_FATAL_IF(packageChars.size() <= 0 || !packageChars.c_str(), "Failed to get path chars"); - - const std::string path(pathChars.c_str(), pathChars.size()); - const std::string package(packageChars.c_str(), packageChars.size()); - const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get()); - GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); -} - -static jobject gGraphicsStatsServiceObject = nullptr; -static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID; - -static JNIEnv* getJNIEnv() { - JavaVM* vm = AndroidRuntime::getJavaVM(); - JNIEnv* env = nullptr; - if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { - if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { - LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); - } - } - return env; -} - -using namespace google::protobuf; - -// Field ids taken from FrameTimingHistogram message in atoms.proto -#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1 -#define FRAME_COUNTS_FIELD_NUMBER 2 - -static void writeCpuHistogram(AStatsEvent* event, - const uirenderer::protos::GraphicsStatsProto& stat) { - util::ProtoOutputStream proto; - for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { - auto& bucket = stat.histogram(bucketIndex); - proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | - TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, - (int)bucket.render_millis()); - } - for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { - auto& bucket = stat.histogram(bucketIndex); - proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | - FRAME_COUNTS_FIELD_NUMBER /* field id */, - (long long)bucket.frame_count()); - } - std::vector<uint8_t> outVector; - proto.serializeToVector(&outVector); - AStatsEvent_writeByteArray(event, outVector.data(), outVector.size()); -} - -static void writeGpuHistogram(AStatsEvent* event, - const uirenderer::protos::GraphicsStatsProto& stat) { - util::ProtoOutputStream proto; - for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { - auto& bucket = stat.gpu_histogram(bucketIndex); - proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | - TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, - (int)bucket.render_millis()); - } - for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { - auto& bucket = stat.gpu_histogram(bucketIndex); - proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | - FRAME_COUNTS_FIELD_NUMBER /* field id */, - (long long)bucket.frame_count()); - } - std::vector<uint8_t> outVector; - proto.serializeToVector(&outVector); - AStatsEvent_writeByteArray(event, outVector.data(), outVector.size()); -} - -// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. -static AStatsManager_PullAtomCallbackReturn graphicsStatsPullCallback(int32_t atom_tag, - AStatsEventList* data, - void* cookie) { - JNIEnv* env = getJNIEnv(); - if (!env) { - return false; - } - if (gGraphicsStatsServiceObject == nullptr) { - ALOGE("Failed to get graphicsstats service"); - return AStatsManager_PULL_SKIP; - } - - for (bool lastFullDay : {true, false}) { - jlong jdata = (jlong) env->CallLongMethod( - gGraphicsStatsServiceObject, - gGraphicsStatsService_pullGraphicsStatsMethodID, - (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE)); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - ALOGE("Failed to invoke graphicsstats service"); - return AStatsManager_PULL_SKIP; - } - if (!jdata) { - // null means data is not available for that day. - continue; - } - android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump; - std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata); - std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer); - int dataSize = buffer->size(); - if (!dataSize) { - // Data is not available for that day. - continue; - } - io::ArrayInputStream input{buffer->data(), dataSize}; - bool success = serviceDump.ParseFromZeroCopyStream(&input); - if (!success) { - ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'", - serviceDump.InitializationErrorString().c_str(), dataSize); - return AStatsManager_PULL_SKIP; - } - - for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { - auto& stat = serviceDump.stats(stat_index); - AStatsEvent* event = AStatsEventList_addStatsEvent(data); - AStatsEvent_setAtomId(event, android::util::GRAPHICS_STATS); - AStatsEvent_writeString(event, stat.package_name().c_str()); - AStatsEvent_writeInt64(event, (int64_t)stat.version_code()); - AStatsEvent_writeInt64(event, (int64_t)stat.stats_start()); - AStatsEvent_writeInt64(event, (int64_t)stat.stats_end()); - AStatsEvent_writeInt32(event, (int32_t)stat.pipeline()); - AStatsEvent_writeInt32(event, (int32_t)stat.summary().total_frames()); - AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_vsync_count()); - AStatsEvent_writeInt32(event, (int32_t)stat.summary().high_input_latency_count()); - AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_ui_thread_count()); - AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); - AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_draw_count()); - AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_deadline_count()); - writeCpuHistogram(event, stat); - writeGpuHistogram(event, stat); - // TODO: fill in UI mainline module version, when the feature is available. - AStatsEvent_writeInt64(event, (int64_t)0); - AStatsEvent_writeBool(event, !lastFullDay); - AStatsEvent_build(event); - } - } - return AStatsManager_PULL_SUCCESS; -} - -// Register a puller for GRAPHICS_STATS atom with the statsd service. -static void nativeInit(JNIEnv* env, jobject javaObject) { - gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject); - AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain(); - AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, 10 * 1000000); // 10 milliseconds - AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, 2 * NS_PER_SEC); // 2 seconds - - AStatsManager_registerPullAtomCallback(android::util::GRAPHICS_STATS, - &graphicsStatsPullCallback, metadata, nullptr); - - AStatsManager_PullAtomMetadata_release(metadata); -} - -static void nativeDestructor(JNIEnv* env, jobject javaObject) { - AStatsManager_unregisterPullAtomCallback(android::util::GRAPHICS_STATS); - env->DeleteGlobalRef(gGraphicsStatsServiceObject); - gGraphicsStatsServiceObject = nullptr; -} - -static const JNINativeMethod sMethods[] = { - { "nGetAshmemSize", "()I", (void*) getAshmemSize }, - { "nCreateDump", "(IZ)J", (void*) createDump }, - { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump }, - { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump }, - { "nFinishDump", "(J)V", (void*) finishDump }, - { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory }, - { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer }, - { "nativeInit", "()V", (void*) nativeInit }, - { "nativeDestructor", "()V", (void*)nativeDestructor } -}; - -int register_android_server_GraphicsStatsService(JNIEnv* env) -{ - jclass graphicsStatsService_class = FindClassOrDie(env, - "com/android/server/GraphicsStatsService"); - gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env, - graphicsStatsService_class, "pullGraphicsStats", "(Z)J"); - return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService", - sMethods, NELEM(sMethods)); -} - -} // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 1202ad33996d..c1864945f921 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -49,7 +49,7 @@ int register_android_server_PersistentDataBlockService(JNIEnv* env); int register_android_server_Watchdog(JNIEnv* env); int register_android_server_HardwarePropertiesManagerService(JNIEnv* env); int register_android_server_SyntheticPasswordManager(JNIEnv* env); -int register_android_server_GraphicsStatsService(JNIEnv* env); +int register_android_graphics_GraphicsStatsService(JNIEnv* env); int register_android_hardware_display_DisplayViewport(JNIEnv* env); int register_android_server_net_NetworkStatsFactory(JNIEnv* env); int register_android_server_net_NetworkStatsService(JNIEnv* env); @@ -102,7 +102,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_HardwarePropertiesManagerService(env); register_android_server_storage_AppFuse(env); register_android_server_SyntheticPasswordManager(env); - register_android_server_GraphicsStatsService(env); + register_android_graphics_GraphicsStatsService(env); register_android_hardware_display_DisplayViewport(env); register_android_server_net_NetworkStatsFactory(env); register_android_server_net_NetworkStatsService(env); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 553ec4201cc2..cb687c9144c6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -458,8 +458,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // A collection of user restrictions that are deprecated and should simply be ignored. private static final Set<String> DEPRECATED_USER_RESTRICTIONS; private static final String AB_DEVICE_KEY = "ro.build.ab_update"; - // Permissions related to location which must not be granted automatically - private static final Set<String> LOCATION_PERMISSIONS; static { SECURE_SETTINGS_WHITELIST = new ArraySet<>(); @@ -504,11 +502,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { DEPRECATED_USER_RESTRICTIONS = Sets.newHashSet( UserManager.DISALLOW_ADD_MANAGED_PROFILE, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE); - - LOCATION_PERMISSIONS = Sets.newHashSet( - permission.ACCESS_FINE_LOCATION, - permission.ACCESS_BACKGROUND_LOCATION, - permission.ACCESS_COARSE_LOCATION); } /** @@ -560,6 +553,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private static final long ADMIN_APP_PASSWORD_COMPLEXITY = 123562444L; + /** + * Admin apps targeting Android R+ may not use + * {@link android.app.admin.DevicePolicyManager#setSecureSetting} to change the deprecated + * {@link android.provider.Settings.Secure#LOCATION_MODE} setting. Instead they should use + * {@link android.app.admin.DevicePolicyManager#setLocationEnabled}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long USE_SET_LOCATION_ENABLED = 117835097L; + final Context mContext; final Injector mInjector; final IPackageManager mIPackageManager; @@ -8520,6 +8523,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private void clearOverrideApnUnchecked() { + if (!mHasTelephonyFeature) { + return; + } // Disable Override APNs and remove them from database. setOverrideApnsEnabledUnchecked(false); final List<ApnSetting> apns = getOverrideApnsUnchecked(); @@ -11558,13 +11564,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setLocationEnabled(ComponentName who, boolean locationEnabled) { - Objects.requireNonNull(who, "ComponentName is null"); - enforceDeviceOwner(who); + enforceDeviceOwner(Objects.requireNonNull(who)); - UserHandle userHandle = mInjector.binderGetCallingUserHandle(); - mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, - userHandle)); + UserHandle user = mInjector.binderGetCallingUserHandle(); + + mInjector.binderWithCleanCallingIdentity(() -> { + boolean wasLocationEnabled = mInjector.getLocationManager().isLocationEnabledForUser( + user); + mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, user); + + // make a best effort to only show the notification if the admin is actually changing + // something. this is subject to race conditions with settings changes, but those are + // unlikely to realistically interfere + if (wasLocationEnabled != locationEnabled) { + showLocationSettingsChangedNotification(user); + } + }); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_SECURE_SETTING) @@ -11575,6 +11590,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } + private void showLocationSettingsChangedNotification(UserHandle user) { + PendingIntent locationSettingsIntent = mInjector.pendingIntentGetActivityAsUser(mContext, 0, + new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_UPDATE_CURRENT, + null, user); + Notification notification = new Notification.Builder(mContext, + SystemNotificationChannels.DEVICE_ADMIN) + .setSmallIcon(R.drawable.ic_info_outline) + .setContentTitle(mContext.getString(R.string.location_changed_notification_title)) + .setContentText(mContext.getString(R.string.location_changed_notification_text)) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setShowWhen(true) + .setContentIntent(locationSettingsIntent) + .setAutoCancel(true) + .build(); + mInjector.getNotificationManager().notify(SystemMessage.NOTE_LOCATION_CHANGED, + notification); + } + @Override public void requestSetLocationProviderAllowed(ComponentName who, String provider, boolean providerAllowed) { @@ -11645,6 +11679,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new SecurityException(String.format( "Permission denial: Profile owners cannot update %1$s", setting)); } + if (setting.equals(Settings.Secure.LOCATION_MODE) + && isSetSecureSettingLocationModeCheckEnabled(who.getPackageName(), + callingUserId)) { + throw new UnsupportedOperationException(Settings.Secure.LOCATION_MODE + " is " + + "deprecated. Please use setLocationEnabled() instead."); + } if (setting.equals(Settings.Secure.INSTALL_NON_MARKET_APPS)) { if (getTargetSdk(who.getPackageName(), callingUserId) >= Build.VERSION_CODES.O) { throw new UnsupportedOperationException(Settings.Secure.INSTALL_NON_MARKET_APPS @@ -11689,6 +11729,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(callingUserId); } mInjector.settingsSecurePutStringForUser(setting, value, callingUserId); + if (setting.equals(Settings.Secure.LOCATION_MODE)) { + showLocationSettingsChangedNotification(UserHandle.of(callingUserId)); + } }); } DevicePolicyEventLogger @@ -11698,6 +11741,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } + private boolean isSetSecureSettingLocationModeCheckEnabled(String packageName, int userId) { + try { + return mIPlatformCompat.isChangeEnabledByPackageName(USE_SET_LOCATION_ENABLED, + packageName, userId); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e); + return getTargetSdk(packageName, userId) > Build.VERSION_CODES.Q; + } + } + @Override public void setMasterVolumeMuted(ComponentName who, boolean on) { Objects.requireNonNull(who, "ComponentName is null"); @@ -12541,14 +12594,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { true); } - // Prevent granting location-related permissions without user consent. - if (LOCATION_PERMISSIONS.contains(permission) - && grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED - && !isUnattendedManagedKioskUnchecked()) { - callback.sendResult(null); - return; - } - if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 0941831f5299..b2c316a25e7f 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -16,6 +16,7 @@ #include "BinderIncrementalService.h" +#include <android-base/logging.h> #include <binder/IResultReceiver.h> #include <binder/PermissionCache.h> #include <incfs.h> @@ -24,7 +25,6 @@ #include "jni.h" #include "nativehelper/JNIHelp.h" #include "path.h" -#include <android-base/logging.h> using namespace std::literals; using namespace android::incremental; @@ -277,6 +277,13 @@ binder::Status BinderIncrementalService::startLoading(int32_t storageId, bool* _ return ok(); } +binder::Status BinderIncrementalService::configureNativeBinaries( + int32_t storageId, const std::string& apkFullPath, const std::string& libDirRelativePath, + const std::string& abi, bool* _aidl_return) { + *_aidl_return = mImpl.configureNativeBinaries(storageId, apkFullPath, libDirRelativePath, abi); + return ok(); +} + } // namespace android::os::incremental jlong Incremental_IncrementalService_Start() { diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index 8a099776b54b..51d7de3d9adf 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -28,11 +28,11 @@ namespace android::os::incremental { class BinderIncrementalService : public BnIncrementalService, public BinderService<BinderIncrementalService> { public: - BinderIncrementalService(const sp<IServiceManager> &sm); + BinderIncrementalService(const sp<IServiceManager>& sm); - static BinderIncrementalService *start(); - static const char16_t *getServiceName() { return u"incremental_service"; } - status_t dump(int fd, const Vector<String16> &args) final; + static BinderIncrementalService* start(); + static const char16_t* getServiceName() { return u"incremental_service"; } + status_t dump(int fd, const Vector<String16>& args) final; void onSystemReady(); void onInvalidStorage(int mountId); @@ -70,6 +70,9 @@ public: std::vector<uint8_t>* _aidl_return) final; binder::Status startLoading(int32_t storageId, bool* _aidl_return) final; binder::Status deleteStorage(int32_t storageId) final; + binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath, + const std::string& libDirRelativePath, + const std::string& abi, bool* _aidl_return) final; private: android::incremental::IncrementalService mImpl; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index dbd97cfa039a..8012e74a6b3c 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -60,6 +60,9 @@ struct Constants { static constexpr auto storagePrefix = "st"sv; static constexpr auto mountpointMdPrefix = ".mountpoint."sv; static constexpr auto infoMdName = ".info"sv; + static constexpr auto libDir = "lib"sv; + static constexpr auto libSuffix = ".so"sv; + static constexpr auto blockSize = 4096; }; static const Constants& constants() { @@ -259,16 +262,18 @@ IncrementalService::~IncrementalService() = default; inline const char* toString(TimePoint t) { using SystemClock = std::chrono::system_clock; - time_t time = SystemClock::to_time_t(SystemClock::now() + std::chrono::duration_cast<SystemClock::duration>(t - Clock::now())); + time_t time = SystemClock::to_time_t( + SystemClock::now() + + std::chrono::duration_cast<SystemClock::duration>(t - Clock::now())); return std::ctime(&time); } inline const char* toString(IncrementalService::BindKind kind) { switch (kind) { - case IncrementalService::BindKind::Temporary: - return "Temporary"; - case IncrementalService::BindKind::Permanent: - return "Permanent"; + case IncrementalService::BindKind::Temporary: + return "Temporary"; + case IncrementalService::BindKind::Permanent: + return "Permanent"; } } @@ -1124,6 +1129,122 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, return true; } +// Extract lib filse from zip, create new files in incfs and write data to them +bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_view apkFullPath, + std::string_view libDirRelativePath, + std::string_view abi) { + const auto ifs = getIfs(storage); + // First prepare target directories if they don't exist yet + if (auto res = makeDirs(storage, libDirRelativePath, 0755)) { + LOG(ERROR) << "Failed to prepare target lib directory " << libDirRelativePath + << " errno: " << res; + return false; + } + + std::unique_ptr<ZipFileRO> zipFile(ZipFileRO::open(apkFullPath.data())); + if (!zipFile) { + LOG(ERROR) << "Failed to open zip file at " << apkFullPath; + return false; + } + void* cookie = nullptr; + const auto libFilePrefix = path::join(constants().libDir, abi); + if (!zipFile.get()->startIteration(&cookie, libFilePrefix.c_str() /* prefix */, + constants().libSuffix.data() /* suffix */)) { + LOG(ERROR) << "Failed to start zip iteration for " << apkFullPath; + return false; + } + ZipEntryRO entry = nullptr; + bool success = true; + while ((entry = zipFile.get()->nextEntry(cookie)) != nullptr) { + char fileName[PATH_MAX]; + if (zipFile.get()->getEntryFileName(entry, fileName, sizeof(fileName))) { + continue; + } + const auto libName = path::basename(fileName); + const auto targetLibPath = path::join(libDirRelativePath, libName); + const auto targetLibPathAbsolute = normalizePathToStorage(ifs, storage, targetLibPath); + // If the extract file already exists, skip + struct stat st; + if (stat(targetLibPathAbsolute.c_str(), &st) == 0) { + LOG(INFO) << "Native lib file already exists: " << targetLibPath + << "; skipping extraction"; + continue; + } + + uint32_t uncompressedLen; + if (!zipFile.get()->getEntryInfo(entry, nullptr, &uncompressedLen, nullptr, nullptr, + nullptr, nullptr)) { + LOG(ERROR) << "Failed to read native lib entry: " << fileName; + success = false; + break; + } + + // Create new lib file without signature info + incfs::NewFileParams libFileParams{}; + libFileParams.size = uncompressedLen; + libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE; + // Metadata of the new lib file is its relative path + IncFsSpan libFileMetadata; + libFileMetadata.data = targetLibPath.c_str(); + libFileMetadata.size = targetLibPath.size(); + libFileParams.metadata = libFileMetadata; + incfs::FileId libFileId = idFromMetadata(targetLibPath); + if (auto res = makeFile(storage, targetLibPath, 0777, libFileId, libFileParams)) { + LOG(ERROR) << "Failed to make file for: " << targetLibPath << " errno: " << res; + success = false; + // If one lib file fails to be created, abort others as well + break; + } + + // Write extracted data to new file + std::vector<uint8_t> libData(uncompressedLen); + if (!zipFile.get()->uncompressEntry(entry, &libData[0], uncompressedLen)) { + LOG(ERROR) << "Failed to extract native lib zip entry: " << fileName; + success = false; + break; + } + android::base::unique_fd writeFd(mIncFs->openWrite(ifs->control, libFileId)); + if (writeFd < 0) { + LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd; + success = false; + break; + } + const int numBlocks = uncompressedLen / constants().blockSize + 1; + std::vector<IncFsDataBlock> instructions; + auto remainingData = std::span(libData); + for (int i = 0; i < numBlocks - 1; i++) { + auto inst = IncFsDataBlock{ + .fileFd = writeFd, + .pageIndex = static_cast<IncFsBlockIndex>(i), + .compression = INCFS_COMPRESSION_KIND_NONE, + .kind = INCFS_BLOCK_KIND_DATA, + .dataSize = static_cast<uint16_t>(constants().blockSize), + .data = reinterpret_cast<const char*>(remainingData.data()), + }; + instructions.push_back(inst); + remainingData = remainingData.subspan(constants().blockSize); + } + // Last block + auto inst = IncFsDataBlock{ + .fileFd = writeFd, + .pageIndex = static_cast<IncFsBlockIndex>(numBlocks - 1), + .compression = INCFS_COMPRESSION_KIND_NONE, + .kind = INCFS_BLOCK_KIND_DATA, + .dataSize = static_cast<uint16_t>(remainingData.size()), + .data = reinterpret_cast<const char*>(remainingData.data()), + }; + instructions.push_back(inst); + size_t res = mIncFs->writeBlocks(instructions); + if (res != instructions.size()) { + LOG(ERROR) << "Failed to write data into: " << targetLibPath; + success = false; + } + instructions.clear(); + } + zipFile.get()->endIteration(cookie); + return success; +} + binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId, int newStatus) { std::unique_lock l(incrementalService.mLock); diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index dec9f64f2084..2e7ced37e91d 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -122,11 +122,11 @@ public: } 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; - + bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath, + std::string_view libDirRelativePath, std::string_view abi); class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener { public: IncrementalDataLoaderListener(IncrementalService& incrementalService) diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 036335ca9011..eef6c63d29c9 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -41,6 +41,7 @@ import android.content.res.Configuration; import android.content.res.Resources.Theme; import android.database.sqlite.SQLiteCompatibilityWalFlags; import android.database.sqlite.SQLiteGlobal; +import android.graphics.GraphicsStatsService; import android.hardware.display.DisplayManagerInternal; import android.net.ConnectivityModuleConnector; import android.net.ITetheringConnector; diff --git a/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java b/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java new file mode 100644 index 000000000000..203e9804bfa3 --- /dev/null +++ b/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java @@ -0,0 +1,264 @@ +/* + * 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.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.text.format.DateUtils; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Base class for reading and writing protobufs on disk from a root directory. Callers should + * ensure that the root directory is unlocked before doing I/O operations using this class. + * + * @param <T> is the data class representation of a protobuf. + */ +abstract class AbstractProtoDiskReadWriter<T> { + + private static final String TAG = AbstractProtoDiskReadWriter.class.getSimpleName(); + private static final long SHUTDOWN_DISK_WRITE_TIMEOUT = 5L * DateUtils.SECOND_IN_MILLIS; + + private final File mRootDir; + private final ScheduledExecutorService mScheduledExecutorService; + private final long mWriteDelayMs; + + @GuardedBy("this") + private ScheduledFuture<?> mScheduledFuture; + + @GuardedBy("this") + private Map<String, T> mScheduledFileDataMap = new ArrayMap<>(); + + /** + * Child class shall provide a {@link ProtoStreamWriter} to facilitate the writing of data as a + * protobuf on disk. + */ + abstract ProtoStreamWriter<T> protoStreamWriter(); + + /** + * Child class shall provide a {@link ProtoStreamReader} to facilitate the reading of protobuf + * data on disk. + */ + abstract ProtoStreamReader<T> protoStreamReader(); + + AbstractProtoDiskReadWriter(@NonNull File rootDir, long writeDelayMs, + @NonNull ScheduledExecutorService scheduledExecutorService) { + mRootDir = rootDir; + mWriteDelayMs = writeDelayMs; + mScheduledExecutorService = scheduledExecutorService; + } + + @WorkerThread + void delete(@NonNull String fileName) { + final File file = getFile(fileName); + if (!file.exists()) { + return; + } + if (!file.delete()) { + Slog.e(TAG, "Failed to delete file: " + file.getPath()); + } + } + + @WorkerThread + void writeTo(@NonNull String fileName, @NonNull T data) { + final File file = getFile(fileName); + final AtomicFile atomicFile = new AtomicFile(file); + + FileOutputStream fileOutputStream = null; + try { + fileOutputStream = atomicFile.startWrite(); + } catch (IOException e) { + Slog.e(TAG, "Failed to write to protobuf file.", e); + return; + } + + try { + final ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); + protoStreamWriter().write(protoOutputStream, data); + protoOutputStream.flush(); + atomicFile.finishWrite(fileOutputStream); + fileOutputStream = null; + } finally { + // When fileInputStream is null (successful write), this will no-op. + atomicFile.failWrite(fileOutputStream); + } + } + + @WorkerThread + @Nullable + T read(@NonNull String fileName) { + File[] files = mRootDir.listFiles( + pathname -> pathname.isFile() && pathname.getName().equals(fileName)); + if (files == null || files.length == 0) { + return null; + } else if (files.length > 1) { + // This can't possibly happen, but sanity check. + Slog.w(TAG, "Found multiple files with the same name: " + Arrays.toString(files)); + } + return parseFile(files[0]); + } + + /** + * Reads all files in directory and returns a map with file names as keys and parsed file + * contents as values. + */ + @WorkerThread + @Nullable + Map<String, T> readAll() { + File[] files = mRootDir.listFiles(File::isFile); + if (files == null) { + return null; + } + + Map<String, T> results = new ArrayMap<>(); + for (File file : files) { + T result = parseFile(file); + if (result != null) { + results.put(file.getName(), result); + } + } + return results; + } + + /** + * Schedules the specified data to be flushed to a file in the future. Subsequent + * calls for the same file before the flush occurs will replace the previous data but will not + * reset when the flush will occur. All unique files will be flushed at the same time. + */ + @MainThread + synchronized void scheduleSave(@NonNull String fileName, @NonNull T data) { + mScheduledFileDataMap.put(fileName, data); + + if (mScheduledExecutorService.isShutdown()) { + Slog.e(TAG, "Worker is shutdown, failed to schedule data saving."); + return; + } + + // Skip scheduling another flush when one is pending. + if (mScheduledFuture != null) { + return; + } + + mScheduledFuture = mScheduledExecutorService.schedule(this::flushScheduledData, + mWriteDelayMs, TimeUnit.MILLISECONDS); + } + + /** + * Saves specified data immediately on a background thread, and blocks until its completed. This + * is useful for when device is powering off. + */ + @MainThread + synchronized void saveImmediately(@NonNull String fileName, @NonNull T data) { + if (mScheduledExecutorService.isShutdown()) { + return; + } + // Cancel existing future. + if (mScheduledFuture != null) { + + // We shouldn't need to interrupt as this method and threaded task + // #flushScheduledData are both synchronized. + mScheduledFuture.cancel(true); + } + + mScheduledFileDataMap.put(fileName, data); + // Submit flush and blocks until it completes. Blocking will prevent the device from + // shutting down before flushing completes. + Future<?> future = mScheduledExecutorService.submit(this::flushScheduledData); + try { + future.get(SHUTDOWN_DISK_WRITE_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Slog.e(TAG, "Failed to save data immediately.", e); + } + } + + @WorkerThread + private synchronized void flushScheduledData() { + if (mScheduledFileDataMap.isEmpty()) { + mScheduledFuture = null; + return; + } + for (String fileName : mScheduledFileDataMap.keySet()) { + T data = mScheduledFileDataMap.remove(fileName); + writeTo(fileName, data); + } + mScheduledFuture = null; + } + + @WorkerThread + @Nullable + private T parseFile(@NonNull File file) { + final AtomicFile atomicFile = new AtomicFile(file); + try (FileInputStream fileInputStream = atomicFile.openRead()) { + final ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); + return protoStreamReader().read(protoInputStream); + } catch (IOException e) { + Slog.e(TAG, "Failed to parse protobuf file.", e); + } + return null; + } + + @NonNull + private File getFile(String fileName) { + return new File(mRootDir, fileName); + } + + /** + * {@code ProtoStreamWriter} writes {@code T} fields to {@link ProtoOutputStream}. + * + * @param <T> is the data class representation of a protobuf. + */ + interface ProtoStreamWriter<T> { + + /** + * Writes {@code T} to {@link ProtoOutputStream}. + */ + void write(@NonNull ProtoOutputStream protoOutputStream, @NonNull T data); + } + + /** + * {@code ProtoStreamReader} reads {@link ProtoInputStream} and translate it to {@code T}. + * + * @param <T> is the data class representation of a protobuf. + */ + interface ProtoStreamReader<T> { + /** + * Reads {@link ProtoInputStream} and translates it to {@code T}. + */ + @Nullable + T read(@NonNull ProtoInputStream protoInputStream); + } +} diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java index bb97533b3222..ce353667b62d 100644 --- a/services/people/java/com/android/server/people/data/ConversationInfo.java +++ b/services/people/java/com/android/server/people/data/ConversationInfo.java @@ -20,12 +20,18 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.LocusId; +import android.content.LocusIdProto; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo.ShortcutFlags; import android.net.Uri; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; +import com.android.server.people.ConversationInfoProto; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -35,7 +41,9 @@ import java.util.Objects; */ public class ConversationInfo { - private static final int FLAG_VIP = 1; + private static final String TAG = ConversationInfo.class.getSimpleName(); + + private static final int FLAG_IMPORTANT = 1; private static final int FLAG_NOTIFICATION_SILENCED = 1 << 1; @@ -50,7 +58,7 @@ public class ConversationInfo { private static final int FLAG_DEMOTED = 1 << 6; @IntDef(flag = true, prefix = {"FLAG_"}, value = { - FLAG_VIP, + FLAG_IMPORTANT, FLAG_NOTIFICATION_SILENCED, FLAG_BUBBLED, FLAG_PERSON_IMPORTANT, @@ -129,9 +137,9 @@ public class ConversationInfo { return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED); } - /** Whether this conversation is marked as VIP by the user. */ - public boolean isVip() { - return hasConversationFlags(FLAG_VIP); + /** Whether this conversation is marked as important by the user. */ + public boolean isImportant() { + return hasConversationFlags(FLAG_IMPORTANT); } /** Whether the notifications for this conversation should be silenced. */ @@ -208,8 +216,8 @@ public class ConversationInfo { sb.append("]"); sb.append(", conversationFlags=0x").append(Integer.toHexString(mConversationFlags)); sb.append(" ["); - if (isVip()) { - sb.append("Vip"); + if (isImportant()) { + sb.append("Imp"); } if (isNotificationSilenced()) { sb.append("Sil"); @@ -221,7 +229,7 @@ public class ConversationInfo { sb.append("Dem"); } if (isPersonImportant()) { - sb.append("Imp"); + sb.append("PIm"); } if (isPersonBot()) { sb.append("Bot"); @@ -241,6 +249,72 @@ public class ConversationInfo { return (mConversationFlags & flags) == flags; } + /** Writes field members to {@link ProtoOutputStream}. */ + void writeToProto(@NonNull ProtoOutputStream protoOutputStream) { + protoOutputStream.write(ConversationInfoProto.SHORTCUT_ID, mShortcutId); + if (mLocusId != null) { + long locusIdToken = protoOutputStream.start(ConversationInfoProto.LOCUS_ID_PROTO); + protoOutputStream.write(LocusIdProto.LOCUS_ID, mLocusId.getId()); + protoOutputStream.end(locusIdToken); + } + if (mContactUri != null) { + protoOutputStream.write(ConversationInfoProto.CONTACT_URI, mContactUri.toString()); + } + if (mNotificationChannelId != null) { + protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID, + mNotificationChannelId); + } + protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags); + protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags); + } + + /** Reads from {@link ProtoInputStream} and constructs a {@link ConversationInfo}. */ + @NonNull + static ConversationInfo readFromProto(@NonNull ProtoInputStream protoInputStream) + throws IOException { + ConversationInfo.Builder builder = new ConversationInfo.Builder(); + while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (protoInputStream.getFieldNumber()) { + case (int) ConversationInfoProto.SHORTCUT_ID: + builder.setShortcutId( + protoInputStream.readString(ConversationInfoProto.SHORTCUT_ID)); + break; + case (int) ConversationInfoProto.LOCUS_ID_PROTO: + long locusIdToken = protoInputStream.start( + ConversationInfoProto.LOCUS_ID_PROTO); + while (protoInputStream.nextField() + != ProtoInputStream.NO_MORE_FIELDS) { + if (protoInputStream.getFieldNumber() == (int) LocusIdProto.LOCUS_ID) { + builder.setLocusId(new LocusId( + protoInputStream.readString(LocusIdProto.LOCUS_ID))); + } + } + protoInputStream.end(locusIdToken); + break; + case (int) ConversationInfoProto.CONTACT_URI: + builder.setContactUri(Uri.parse(protoInputStream.readString( + ConversationInfoProto.CONTACT_URI))); + break; + case (int) ConversationInfoProto.NOTIFICATION_CHANNEL_ID: + builder.setNotificationChannelId(protoInputStream.readString( + ConversationInfoProto.NOTIFICATION_CHANNEL_ID)); + break; + case (int) ConversationInfoProto.SHORTCUT_FLAGS: + builder.setShortcutFlags(protoInputStream.readInt( + ConversationInfoProto.SHORTCUT_FLAGS)); + break; + case (int) ConversationInfoProto.CONVERSATION_FLAGS: + builder.setConversationFlags(protoInputStream.readInt( + ConversationInfoProto.CONVERSATION_FLAGS)); + break; + default: + Slog.w(TAG, "Could not read undefined field: " + + protoInputStream.getFieldNumber()); + } + } + return builder.build(); + } + /** * Builder class for {@link ConversationInfo} objects. */ @@ -318,8 +392,8 @@ public class ConversationInfo { return this; } - Builder setVip(boolean value) { - return setConversationFlag(FLAG_VIP, value); + Builder setImportant(boolean value) { + return setConversationFlag(FLAG_IMPORTANT, value); } Builder setNotificationSilenced(boolean value) { diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java index 364992181f75..ea36d38e5d4a 100644 --- a/services/people/java/com/android/server/people/data/ConversationStore.java +++ b/services/people/java/com/android/server/people/data/ConversationStore.java @@ -16,58 +16,124 @@ package com.android.server.people.data; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.WorkerThread; import android.content.LocusId; import android.net.Uri; +import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.ArrayMap; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import com.android.internal.annotations.GuardedBy; +import com.android.server.people.ConversationInfosProto; + +import com.google.android.collect.Lists; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; -/** The store that stores and accesses the conversations data for a package. */ +/** + * The store that stores and accesses the conversations data for a package. + */ class ConversationStore { + private static final String TAG = ConversationStore.class.getSimpleName(); + + private static final String CONVERSATIONS_FILE_NAME = "conversations"; + + private static final long DISK_WRITE_DELAY = 2L * DateUtils.MINUTE_IN_MILLIS; + // Shortcut ID -> Conversation Info + @GuardedBy("this") private final Map<String, ConversationInfo> mConversationInfoMap = new ArrayMap<>(); // Locus ID -> Shortcut ID + @GuardedBy("this") private final Map<LocusId, String> mLocusIdToShortcutIdMap = new ArrayMap<>(); // Contact URI -> Shortcut ID + @GuardedBy("this") private final Map<Uri, String> mContactUriToShortcutIdMap = new ArrayMap<>(); // Phone Number -> Shortcut ID + @GuardedBy("this") private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>(); // Notification Channel ID -> Shortcut ID + @GuardedBy("this") private final Map<String, String> mNotifChannelIdToShortcutIdMap = new ArrayMap<>(); - void addOrUpdate(@NonNull ConversationInfo conversationInfo) { - mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo); + private final ScheduledExecutorService mScheduledExecutorService; + private final File mPackageDir; + private final ContactsQueryHelper mHelper; - LocusId locusId = conversationInfo.getLocusId(); - if (locusId != null) { - mLocusIdToShortcutIdMap.put(locusId, conversationInfo.getShortcutId()); - } + private ConversationInfosProtoDiskReadWriter mConversationInfosProtoDiskReadWriter; - Uri contactUri = conversationInfo.getContactUri(); - if (contactUri != null) { - mContactUriToShortcutIdMap.put(contactUri, conversationInfo.getShortcutId()); - } + ConversationStore(@NonNull File packageDir, + @NonNull ScheduledExecutorService scheduledExecutorService, + @NonNull ContactsQueryHelper helper) { + mScheduledExecutorService = scheduledExecutorService; + mPackageDir = packageDir; + mHelper = helper; + } - String phoneNumber = conversationInfo.getContactPhoneNumber(); - if (phoneNumber != null) { - mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId()); - } + /** + * Loads conversations from disk to memory in a background thread. This should be called + * after the device powers on and the user has been unlocked. + */ + @MainThread + void loadConversationsFromDisk() { + mScheduledExecutorService.submit(() -> { + synchronized (this) { + ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter = + getConversationInfosProtoDiskReadWriter(); + if (conversationInfosProtoDiskReadWriter == null) { + return; + } + List<ConversationInfo> conversationsOnDisk = + conversationInfosProtoDiskReadWriter.read(CONVERSATIONS_FILE_NAME); + if (conversationsOnDisk == null) { + return; + } + for (ConversationInfo conversationInfo : conversationsOnDisk) { + conversationInfo = restoreConversationPhoneNumber(conversationInfo); + updateConversationsInMemory(conversationInfo); + } + } + }); + } - String notifChannelId = conversationInfo.getNotificationChannelId(); - if (notifChannelId != null) { - mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId()); + /** + * Immediately flushes current conversations to disk. This should be called when device is + * powering off. + */ + @MainThread + synchronized void saveConversationsToDisk() { + ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter = + getConversationInfosProtoDiskReadWriter(); + if (conversationInfosProtoDiskReadWriter != null) { + conversationInfosProtoDiskReadWriter.saveConversationsImmediately( + new ArrayList<>(mConversationInfoMap.values())); } } - void deleteConversation(@NonNull String shortcutId) { + @MainThread + synchronized void addOrUpdate(@NonNull ConversationInfo conversationInfo) { + updateConversationsInMemory(conversationInfo); + scheduleUpdateConversationsOnDisk(); + } + + @MainThread + synchronized void deleteConversation(@NonNull String shortcutId) { ConversationInfo conversationInfo = mConversationInfoMap.remove(shortcutId); if (conversationInfo == null) { return; @@ -92,31 +158,32 @@ class ConversationStore { if (notifChannelId != null) { mNotifChannelIdToShortcutIdMap.remove(notifChannelId); } + scheduleUpdateConversationsOnDisk(); } - void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { + synchronized void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { for (ConversationInfo ci : mConversationInfoMap.values()) { consumer.accept(ci); } } @Nullable - ConversationInfo getConversation(@Nullable String shortcutId) { + synchronized ConversationInfo getConversation(@Nullable String shortcutId) { return shortcutId != null ? mConversationInfoMap.get(shortcutId) : null; } @Nullable - ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) { + synchronized ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) { return getConversation(mLocusIdToShortcutIdMap.get(locusId)); } @Nullable - ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) { + synchronized ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) { return getConversation(mContactUriToShortcutIdMap.get(contactUri)); } @Nullable - ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { + synchronized ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber)); } @@ -124,4 +191,140 @@ class ConversationStore { ConversationInfo getConversationByNotificationChannelId(@NonNull String notifChannelId) { return getConversation(mNotifChannelIdToShortcutIdMap.get(notifChannelId)); } + + @MainThread + private synchronized void updateConversationsInMemory( + @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()); + } + + String notifChannelId = conversationInfo.getNotificationChannelId(); + if (notifChannelId != null) { + mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId()); + } + } + + /** Schedules a dump of all conversations onto disk, overwriting existing values. */ + @MainThread + private synchronized void scheduleUpdateConversationsOnDisk() { + ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter = + getConversationInfosProtoDiskReadWriter(); + if (conversationInfosProtoDiskReadWriter != null) { + conversationInfosProtoDiskReadWriter.scheduleConversationsSave( + new ArrayList<>(mConversationInfoMap.values())); + } + } + + @Nullable + private ConversationInfosProtoDiskReadWriter getConversationInfosProtoDiskReadWriter() { + if (!mPackageDir.exists()) { + Slog.e(TAG, "Package data directory does not exist: " + mPackageDir.getAbsolutePath()); + return null; + } + if (mConversationInfosProtoDiskReadWriter == null) { + mConversationInfosProtoDiskReadWriter = new ConversationInfosProtoDiskReadWriter( + mPackageDir, CONVERSATIONS_FILE_NAME, DISK_WRITE_DELAY, + mScheduledExecutorService); + } + return mConversationInfosProtoDiskReadWriter; + } + + /** + * Conversation's phone number is not saved on disk, so it has to be fetched. + */ + @WorkerThread + private ConversationInfo restoreConversationPhoneNumber( + @NonNull ConversationInfo conversationInfo) { + if (conversationInfo.getContactUri() != null) { + if (mHelper.query(conversationInfo.getContactUri().toString())) { + String phoneNumber = mHelper.getPhoneNumber(); + if (!TextUtils.isEmpty(phoneNumber)) { + conversationInfo = new ConversationInfo.Builder( + conversationInfo).setContactPhoneNumber( + phoneNumber).build(); + } + } + } + return conversationInfo; + } + + /** Reads and writes {@link ConversationInfo} on disk. */ + static class ConversationInfosProtoDiskReadWriter extends + AbstractProtoDiskReadWriter<List<ConversationInfo>> { + + private final String mConversationInfoFileName; + + ConversationInfosProtoDiskReadWriter(@NonNull File baseDir, + @NonNull String conversationInfoFileName, + long writeDelayMs, @NonNull ScheduledExecutorService scheduledExecutorService) { + super(baseDir, writeDelayMs, scheduledExecutorService); + mConversationInfoFileName = conversationInfoFileName; + } + + @Override + ProtoStreamWriter<List<ConversationInfo>> protoStreamWriter() { + return (protoOutputStream, data) -> { + for (ConversationInfo conversationInfo : data) { + long token = protoOutputStream.start(ConversationInfosProto.CONVERSATION_INFOS); + conversationInfo.writeToProto(protoOutputStream); + protoOutputStream.end(token); + } + }; + } + + @Override + ProtoStreamReader<List<ConversationInfo>> protoStreamReader() { + return protoInputStream -> { + List<ConversationInfo> results = Lists.newArrayList(); + try { + while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (protoInputStream.getFieldNumber() + != (int) ConversationInfosProto.CONVERSATION_INFOS) { + continue; + } + long token = protoInputStream.start( + ConversationInfosProto.CONVERSATION_INFOS); + ConversationInfo conversationInfo = ConversationInfo.readFromProto( + protoInputStream); + protoInputStream.end(token); + results.add(conversationInfo); + } + } catch (IOException e) { + Slog.e(TAG, "Failed to read protobuf input stream.", e); + } + return results; + }; + } + + /** + * Schedules a flush of the specified conversations to disk. + */ + @MainThread + void scheduleConversationsSave(@NonNull List<ConversationInfo> conversationInfos) { + scheduleSave(mConversationInfoFileName, conversationInfos); + } + + /** + * Saves the specified conversations immediately. This should be used when device is + * powering off. + */ + @MainThread + void saveConversationsImmediately(@NonNull List<ConversationInfo> conversationInfos) { + saveImmediately(mConversationInfoFileName, conversationInfos); + } + } } diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 7fdcf42c6364..c8673f833e71 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -21,6 +21,8 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.Person; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; @@ -84,6 +86,7 @@ public class DataManager { private final Context mContext; private final Injector mInjector; private final ScheduledExecutorService mUsageStatsQueryExecutor; + private final ScheduledExecutorService mDiskReadWriterExecutor; private final SparseArray<UserData> mUserDataArray = new SparseArray<>(); private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>(); @@ -111,6 +114,7 @@ public class DataManager { BackgroundThread.getHandler()); mMmsSmsContentObserver = new MmsSmsContentObserver( BackgroundThread.getHandler()); + mDiskReadWriterExecutor = mInjector.createScheduledExecutor(); } /** Initialization. Called when the system services are up running. */ @@ -120,13 +124,18 @@ public class DataManager { mUserManager = mContext.getSystemService(UserManager.class); mShortcutServiceInternal.addListener(new ShortcutServiceListener()); + + IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); + BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver(); + mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter); } /** 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); + userData = new UserData(userId, mDiskReadWriterExecutor, + mInjector.createContactsQueryHelper(mContext)); mUserDataArray.put(userId, userData); } userData.setUserUnlocked(); @@ -157,8 +166,8 @@ public class DataManager { mNotificationListeners.put(userId, notificationListener); try { notificationListener.registerAsSystemService(mContext, - new ComponentName(PLATFORM_PACKAGE_NAME, getClass().getSimpleName()), - UserHandle.myUserId()); + new ComponentName(PLATFORM_PACKAGE_NAME, getClass().getCanonicalName()), + userId); } catch (RemoteException e) { // Should never occur for local calls. } @@ -571,6 +580,44 @@ public class DataManager { long currentTime = System.currentTimeMillis(); eventHistory.addEvent(new Event(currentTime, Event.TYPE_NOTIFICATION_OPENED)); } + + @Override + public void onNotificationChannelModified(String pkg, UserHandle user, + NotificationChannel channel, int modificationType) { + PackageData packageData = getPackage(pkg, user.getIdentifier()); + String shortcutId = channel.getConversationId(); + if (packageData == null || shortcutId == null) { + return; + } + ConversationStore conversationStore = packageData.getConversationStore(); + ConversationInfo conversationInfo = conversationStore.getConversation(shortcutId); + if (conversationInfo == null) { + return; + } + ConversationInfo.Builder builder = new ConversationInfo.Builder(conversationInfo); + switch (modificationType) { + case NOTIFICATION_CHANNEL_OR_GROUP_ADDED: + case NOTIFICATION_CHANNEL_OR_GROUP_UPDATED: + builder.setNotificationChannelId(channel.getId()); + builder.setImportant(channel.isImportantConversation()); + builder.setDemoted(channel.isDemoted()); + builder.setNotificationSilenced( + channel.getImportance() <= NotificationManager.IMPORTANCE_LOW); + builder.setBubbled(channel.canBubble()); + break; + case NOTIFICATION_CHANNEL_OR_GROUP_DELETED: + // If the notification channel is deleted, revert all the notification settings + // to the default value. + builder.setNotificationChannelId(null); + builder.setImportant(false); + builder.setDemoted(false); + builder.setNotificationSilenced(false); + builder.setBubbled(false); + break; + } + conversationStore.addOrUpdate(builder.build()); + // TODO: Cache the shortcut when a conversation's notification setting is changed. + } } /** @@ -622,6 +669,14 @@ public class DataManager { } } + private class ShutdownBroadcastReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + forAllPackages(PackageData::saveToDisk); + } + } + @VisibleForTesting static class Injector { diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java index 75b870c74591..f67699c28531 100644 --- a/services/people/java/com/android/server/people/data/PackageData.java +++ b/services/people/java/com/android/server/people/data/PackageData.java @@ -22,6 +22,8 @@ import android.annotation.UserIdInt; import android.content.LocusId; import android.text.TextUtils; +import java.io.File; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; import java.util.function.Predicate; @@ -43,17 +45,36 @@ public class PackageData { private final Predicate<String> mIsDefaultSmsAppPredicate; + private final File mPackageDataDir; + PackageData(@NonNull String packageName, @UserIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, - @NonNull Predicate<String> isDefaultSmsAppPredicate) { + @NonNull Predicate<String> isDefaultSmsAppPredicate, + @NonNull ScheduledExecutorService scheduledExecutorService, + @NonNull File perUserPeopleDataDir, + @NonNull ContactsQueryHelper helper) { mPackageName = packageName; mUserId = userId; - mConversationStore = new ConversationStore(); + + mPackageDataDir = new File(perUserPeopleDataDir, mPackageName); + mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService, + helper); mEventStore = new EventStore(); mIsDefaultDialerPredicate = isDefaultDialerPredicate; mIsDefaultSmsAppPredicate = isDefaultSmsAppPredicate; } + /** Called when user is unlocked. */ + void loadFromDisk() { + mPackageDataDir.mkdirs(); + mConversationStore.loadConversationsFromDisk(); + } + + /** Called when device is shutting down. */ + void saveToDisk() { + mConversationStore.saveConversationsToDisk(); + } + @NonNull public String getPackageName() { return mPackageName; diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java index 4e8fd16d05fd..aaa5db878e08 100644 --- a/services/people/java/com/android/server/people/data/UserData.java +++ b/services/people/java/com/android/server/people/data/UserData.java @@ -19,10 +19,13 @@ package com.android.server.people.data; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.os.Environment; import android.text.TextUtils; import android.util.ArrayMap; +import java.io.File; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; /** The data associated with a user profile. */ @@ -30,6 +33,12 @@ class UserData { private final @UserIdInt int mUserId; + private final File mPerUserPeopleDataDir; + + private final ScheduledExecutorService mScheduledExecutorService; + + private final ContactsQueryHelper mHelper; + private boolean mIsUnlocked; private Map<String, PackageData> mPackageDataMap = new ArrayMap<>(); @@ -40,8 +49,12 @@ class UserData { @Nullable private String mDefaultSmsApp; - UserData(@UserIdInt int userId) { + UserData(@UserIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService, + ContactsQueryHelper helper) { mUserId = userId; + mPerUserPeopleDataDir = new File(Environment.getDataSystemCeDirectory(mUserId), "people"); + mScheduledExecutorService = scheduledExecutorService; + mHelper = helper; } @UserIdInt int getUserId() { @@ -56,6 +69,13 @@ class UserData { void setUserUnlocked() { mIsUnlocked = true; + + // Ensures per user root directory for people data is present, and attempt to load + // data from disk. + mPerUserPeopleDataDir.mkdirs(); + for (PackageData packageData : mPackageDataMap.values()) { + packageData.loadFromDisk(); + } } void setUserStopped() { @@ -103,7 +123,8 @@ class UserData { } private PackageData createPackageData(String packageName) { - return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp); + return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp, + mScheduledExecutorService, mPerUserPeopleDataDir, mHelper); } private boolean isDefaultDialer(String packageName) { diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java index 223a98b6c8f6..8daef5fad032 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java @@ -67,8 +67,7 @@ public class ShadowPerformUnifiedRestoreTask { int pmToken, boolean isFullSystemRestore, @Nullable String[] filterSet, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { mBackupManagerService = backupManagerService; mPackage = targetPackage; mIsFullSystemRestore = isFullSystemRestore; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java index 8d2a152dba83..6083ce34a3bd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java @@ -174,12 +174,12 @@ public class ApplicationExitInfoTest { final int app1ConnectiongGroup = 10; final int app1UidUser2 = 1010123; final int app1PidUser2 = 12347; - final int app1Pss1 = 34567; - final int app1Rss1 = 45678; - final int app1Pss2 = 34568; - final int app1Rss2 = 45679; - final int app1Pss3 = 34569; - final int app1Rss3 = 45680; + final long app1Pss1 = 34567; + final long app1Rss1 = 45678; + final long app1Pss2 = 34568; + final long app1Rss2 = 45679; + final long app1Pss3 = 34569; + final long app1Rss3 = 45680; final String app1ProcessName = "com.android.test.stub1:process"; final String app1PackageName = "com.android.test.stub1"; @@ -344,8 +344,8 @@ public class ApplicationExitInfoTest { // Case 4: Create a process from another package with kill from lmkd final int app2UidUser2 = 1010234; final int app2PidUser2 = 12348; - final int app2Pss1 = 54321; - final int app2Rss1 = 65432; + final long app2Pss1 = 54321; + final long app2Rss1 = 65432; final String app2ProcessName = "com.android.test.stub2:process"; final String app2PackageName = "com.android.test.stub2"; @@ -402,8 +402,8 @@ public class ApplicationExitInfoTest { final int app3UidUser2 = 1010345; final int app3PidUser2 = 12349; final int app3ConnectiongGroup = 4; - final int app3Pss1 = 54320; - final int app3Rss1 = 65430; + final long app3Pss1 = 54320; + final long app3Rss1 = 65430; final String app3ProcessName = "com.android.test.stub3:process"; final String app3PackageName = "com.android.test.stub3"; final String app3Description = "native crash"; @@ -529,8 +529,8 @@ public class ApplicationExitInfoTest { final int app3Uid = 10345; final int app3IsolatedUid = 99001; // it's an isolated process final int app3Pid = 12350; - final int app3Pss2 = 23232; - final int app3Rss2 = 32323; + final long app3Pss2 = 23232; + final long app3Rss2 = 32323; final String app3Description2 = "force close"; sleep(1); @@ -618,8 +618,8 @@ public class ApplicationExitInfoTest { sleep(1); final int app1IsolatedUidUser2 = 1099002; // isolated uid - final int app1Pss4 = 34343; - final int app1Rss4 = 43434; + final long app1Pss4 = 34343; + final long app1Rss4 = 43434; final long now8 = System.currentTimeMillis(); sigNum = OsConstants.SIGKILL; doReturn(new Pair<Long, Object>(now8, makeSignalStatus(sigNum))) @@ -673,8 +673,8 @@ public class ApplicationExitInfoTest { sleep(1); final int app1Pid2User2 = 56565; final int app1IsolatedUid2User2 = 1099003; // isolated uid - final int app1Pss5 = 34344; - final int app1Rss5 = 43435; + final long app1Pss5 = 34344; + final long app1Rss5 = 43435; final long now9 = System.currentTimeMillis(); sigNum = OsConstants.SIGKILL; doReturn(new Pair<Long, Object>(now9, makeSignalStatus(sigNum))) @@ -831,7 +831,7 @@ public class ApplicationExitInfoTest { } private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid, - int connectionGroup, int procState, int pss, int rss, + int connectionGroup, int procState, long pss, long rss, String processName, String packageName) { ApplicationInfo ai = new ApplicationInfo(); ai.packageName = packageName; @@ -847,8 +847,8 @@ public class ApplicationExitInfoTest { app.connectionGroup = connectionGroup; app.setProcState = procState; app.lastMemInfo = spy(new Debug.MemoryInfo()); - doReturn(pss).when(app.lastMemInfo).getTotalPss(); - doReturn(rss).when(app.lastMemInfo).getTotalRss(); + doReturn((int) pss).when(app.lastMemInfo).getTotalPss(); + doReturn((int) rss).when(app.lastMemInfo).getTotalRss(); return app; } @@ -856,7 +856,7 @@ public class ApplicationExitInfoTest { Long timestamp, Integer pid, Integer uid, Integer packageUid, Integer definingUid, String processName, Integer connectionGroup, Integer reason, Integer subReason, Integer status, - Integer pss, Integer rss, Integer importance, String description) { + Long pss, Long rss, Integer importance, String description) { assertNotNull(info); if (timestamp != null) { @@ -892,10 +892,10 @@ public class ApplicationExitInfoTest { assertEquals(status.intValue(), info.getStatus()); } if (pss != null) { - assertEquals(pss.intValue(), info.getPss()); + assertEquals(pss.longValue(), info.getPss()); } if (rss != null) { - assertEquals(rss.intValue(), info.getRss()); + assertEquals(rss.longValue(), info.getRss()); } if (importance != null) { assertEquals(importance.intValue(), info.getImportance()); diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java index 25964c592872..3778e17f4320 100644 --- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java @@ -43,7 +43,6 @@ import com.android.server.blob.BlobStoreManagerService.Injector; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -210,7 +209,6 @@ public class BlobStoreManagerServiceTest { verify(file3, never()).delete(); } - @Ignore @Test public void testHandleIdleMaintenance_deleteStaleSessions() throws Exception { // Setup sessions @@ -218,8 +216,8 @@ public class BlobStoreManagerServiceTest { doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS + 1000) .when(sessionFile1).lastModified(); final long sessionId1 = 342; - final BlobHandle blobHandle1 = mock(BlobHandle.class); - doReturn(System.currentTimeMillis() - 1000).when(blobHandle1).getExpiryTimeMillis(); + final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), + "label1", System.currentTimeMillis() - 1000, "tag1"); final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, sessionId1, sessionFile1, blobHandle1); mUserSessions.append(sessionId1, session1); @@ -228,8 +226,8 @@ public class BlobStoreManagerServiceTest { doReturn(System.currentTimeMillis() - 20000) .when(sessionFile2).lastModified(); final long sessionId2 = 4597; - final BlobHandle blobHandle2 = mock(BlobHandle.class); - doReturn(System.currentTimeMillis() + 20000).when(blobHandle2).getExpiryTimeMillis(); + final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), + "label2", System.currentTimeMillis() + 20000, "tag2"); final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2, sessionId2, sessionFile2, blobHandle2); mUserSessions.append(sessionId2, session2); @@ -238,8 +236,8 @@ public class BlobStoreManagerServiceTest { doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS - 2000) .when(sessionFile3).lastModified(); final long sessionId3 = 9484; - final BlobHandle blobHandle3 = mock(BlobHandle.class); - doReturn(System.currentTimeMillis() + 30000).when(blobHandle3).getExpiryTimeMillis(); + final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(), + "label3", System.currentTimeMillis() + 30000, "tag3"); final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3, sessionId3, sessionFile3, blobHandle3); mUserSessions.append(sessionId3, session3); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index f99081024494..bf2b9bec33c9 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -74,7 +74,6 @@ android_test { "libbacktrace", "libbase", "libbinder", - "libbinderthreadstate", "libc++", "libcutils", "liblog", diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java index d6efe35723db..5800acabe8a5 100644 --- a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java @@ -16,8 +16,6 @@ package com.android.server.backup; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import androidx.test.InstrumentationRegistry; @@ -33,18 +31,15 @@ import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; @Presubmit @RunWith(AndroidJUnit4.class) public class UserBackupPreferencesTest { private static final String EXCLUDED_PACKAGE_1 = "package1"; - private static final String EXCLUDED_PACKAGE_2 = "package2"; private static final List<String> EXCLUDED_KEYS_1 = Arrays.asList("key1", "key2"); - private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key1"); + private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key3"); @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); @@ -60,27 +55,13 @@ public class UserBackupPreferencesTest { } @Test - public void testGetExcludedKeysForPackages_returnsExcludedKeys() { + public void testGetExcludedKeysForPackage_returnsExcludedKeys() { mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1); - mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2); + mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_2); - Map<String, Set<String>> excludedKeys = - mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackages(EXCLUDED_PACKAGE_1); - assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1)); - assertFalse(excludedKeys.containsKey(EXCLUDED_PACKAGE_2)); - assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1)); - } - - @Test - public void testGetExcludedKeysForPackages_withEmpty_list_returnsAllExcludedKeys() { - mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1); - mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2); - - Map<String, Set<String>> excludedKeys = - mExcludedRestoreKeysStorage.getAllExcludedRestoreKeys(); - assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1)); - assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_2)); - assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1)); - assertEquals(new HashSet<>(EXCLUDED_KEYS_2), excludedKeys.get(EXCLUDED_PACKAGE_2)); + Set<String> excludedKeys = + mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackage(EXCLUDED_PACKAGE_1); + assertTrue(excludedKeys.containsAll(EXCLUDED_KEYS_1)); + assertTrue(excludedKeys.containsAll(EXCLUDED_KEYS_2)); } } 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 3d220432cc8e..017c93975286 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,9 @@ 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.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import androidx.test.InstrumentationRegistry; @@ -30,6 +32,8 @@ import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.platform.test.annotations.Presubmit; +import com.android.server.backup.UserBackupManagerService; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +44,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.ArrayDeque; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -59,6 +64,7 @@ public class PerformUnifiedRestoreTaskTest { @Mock private BackupDataInput mBackupDataInput; @Mock private BackupDataOutput mBackupDataOutput; + @Mock private UserBackupManagerService mBackupManagerService; private Set<String> mExcludedkeys = new HashSet<>(); private Map<String, String> mBackupData = new HashMap<>(); @@ -99,6 +105,8 @@ public class PerformUnifiedRestoreTaskTest { return null; } }); + + mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService); } private void populateTestData() { @@ -114,8 +122,9 @@ public class PerformUnifiedRestoreTaskTest { @Test public void testFilterExcludedKeys() throws Exception { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( - PACKAGE_NAME, mExcludedkeys)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn( + mExcludedkeys); + mRestoreTask.filterExcludedKeys(PACKAGE_NAME, mBackupDataInput, mBackupDataOutput); // Verify only the correct were written into BackupDataOutput object. @@ -125,32 +134,49 @@ public class PerformUnifiedRestoreTaskTest { } @Test + public void testGetExcludedKeysForPackage_alwaysReturnsLatestKeys() { + Set<String> firstExcludedKeys = new HashSet<>(Collections.singletonList(EXCLUDED_KEY_1)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn( + firstExcludedKeys); + assertEquals(firstExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME)); + + + Set<String> secondExcludedKeys = new HashSet<>(Arrays.asList(EXCLUDED_KEY_1, + EXCLUDED_KEY_2)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn( + secondExcludedKeys); + assertEquals(secondExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME)); + } + + @Test public void testStageBackupData_stageForNonSystemPackageWithKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( - PACKAGE_NAME, mExcludedkeys)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(NON_SYSTEM_PACKAGE_NAME))).thenReturn( + mExcludedkeys); assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); } @Test public void testStageBackupData_stageForNonSystemPackageWithNoKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap()); + when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn( + Collections.emptySet()); assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); } @Test public void testStageBackupData_doNotStageForSystemPackageWithNoKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap()); + when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn( + Collections.emptySet()); assertFalse(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME)); } @Test public void testStageBackupData_stageForSystemPackageWithKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( - PACKAGE_NAME, mExcludedkeys)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(SYSTEM_PACKAGE_NAME))).thenReturn( + mExcludedkeys); - assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); + assertTrue(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME)); } } diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index ce5d6d9be770..4a686ee6a081 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -19,6 +19,7 @@ package com.android.server.compat; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -28,10 +29,12 @@ import static org.testng.Assert.assertThrows; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import androidx.test.runner.AndroidJUnit4; import com.android.internal.compat.AndroidBuildClassifier; +import com.android.server.LocalServices; import org.junit.Before; import org.junit.Test; @@ -48,6 +51,8 @@ public class PlatformCompatTest { @Mock private PackageManager mPackageManager; @Mock + private PackageManagerInternal mPackageManagerInternal; + @Mock CompatChange.ChangeListener mListener1, mListener2; PlatformCompat mPlatformCompat; CompatConfig mCompatConfig; @@ -60,11 +65,15 @@ public class PlatformCompatTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow( new PackageManager.NameNotFoundException()); + when(mPackageManagerInternal.getPackageUid(eq(PACKAGE_NAME), eq(0), anyInt())) + .thenReturn(-1); mCompatConfig = new CompatConfig(mBuildClassifier, mContext); mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); // Assume userdebug/eng non-final build when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); when(mBuildClassifier.isFinalBuild()).thenReturn(false); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); } @Test diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index c9ec87427722..d40130a62fd9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -281,9 +281,9 @@ public class AppIntegrityManagerServiceImplTest { AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); allowedInstallers = allowedInstallersCaptor.getValue(); assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName()); - assertEquals(APP_CERT, appInstallMetadata.getAppCertificate()); + assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT); assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName()); - assertEquals(INSTALLER_CERT, appInstallMetadata.getInstallerCertificate()); + assertThat(appInstallMetadata.getInstallerCertificates()).containsExactly(INSTALLER_CERT); assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode()); assertFalse(appInstallMetadata.isPreInstalled()); // These are hardcoded in the test apk android manifest diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java index 86daf69fb2d9..41be54ab5b7a 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java @@ -141,10 +141,10 @@ public class IntegrityFileManagerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName(packageName) - .setAppCertificate(packageCert) + .setAppCertificates(Collections.singletonList(packageCert)) .setVersionCode(version) .setInstallerName("abc") - .setInstallerCertificate("abc") + .setInstallerCertificates(Collections.singletonList("abc")) .setIsPreInstalled(true) .build(); List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata); @@ -182,10 +182,10 @@ public class IntegrityFileManagerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName(installedPackageName) - .setAppCertificate(installedAppCertificate) + .setAppCertificates(Collections.singletonList(installedAppCertificate)) .setVersionCode(250) .setInstallerName("abc") - .setInstallerCertificate("abc") + .setInstallerCertificates(Collections.singletonList("abc")) .setIsPreInstalled(true) .build(); List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata); diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java index 26b20965fbf5..b0b9596829f1 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluationEngineTest.java @@ -70,17 +70,17 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata1 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); AppInstallMetadata appInstallMetadata2 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_2) - .setInstallerCertificate(INSTALLER_2_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT)) .build(); AppInstallMetadata appInstallMetadata3 = getAppInstallMetadataBuilder() .setInstallerName(RANDOM_INSTALLER) - .setInstallerCertificate(RANDOM_INSTALLER_CERT) + .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect()) @@ -99,7 +99,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata1 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.ALLOW); @@ -107,7 +107,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata2 = getAppInstallMetadataBuilder() .setInstallerName(RANDOM_INSTALLER) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -115,7 +115,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata3 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(RANDOM_INSTALLER_CERT) + .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -123,7 +123,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata4 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(RANDOM_INSTALLER_CERT) + .setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -138,7 +138,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata1 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.ALLOW); @@ -146,7 +146,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata2 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_2) - .setInstallerCertificate(INSTALLER_2_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.ALLOW); @@ -154,7 +154,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata3 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(INSTALLER_2_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -162,7 +162,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata4 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_2) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -178,7 +178,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata1 = getAppInstallMetadataBuilder() .setInstallerName(INSTALLER_1) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.ALLOW); @@ -186,7 +186,7 @@ public class RuleEvaluationEngineTest { AppInstallMetadata appInstallMetadata2 = getAppInstallMetadataBuilder() .setInstallerName(RANDOM_INSTALLER) - .setInstallerCertificate(INSTALLER_1_CERT) + .setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT)) .build(); assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect()) .isEqualTo(IntegrityCheckResult.Effect.DENY); @@ -196,8 +196,8 @@ public class RuleEvaluationEngineTest { private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { return new AppInstallMetadata.Builder() .setPackageName("abc") - .setAppCertificate("abc") - .setInstallerCertificate("abc") + .setAppCertificates(Collections.singletonList("abc")) + .setInstallerCertificates(Collections.singletonList("abc")) .setInstallerName("abc") .setVersionCode(-1) .setIsPreInstalled(true); diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java index 7b53e5e7c762..b271a7766d63 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java @@ -48,7 +48,7 @@ public class RuleEvaluatorTest { private static final AppInstallMetadata APP_INSTALL_METADATA = new AppInstallMetadata.Builder() .setPackageName(PACKAGE_NAME_1) - .setAppCertificate(APP_CERTIFICATE) + .setAppCertificates(Collections.singletonList(APP_CERTIFICATE)) .setVersionCode(2) .build(); diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java index 742952e056bc..0784b7a7d519 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java @@ -35,6 +35,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collections; import java.util.List; @RunWith(JUnit4.class) @@ -49,7 +51,7 @@ public class RuleIndexingControllerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName("ddd") - .setAppCertificate("777") + .setAppCertificates(Collections.singletonList("777")) .build(); List<RuleIndexRange> resultingIndexes = @@ -63,6 +65,29 @@ public class RuleIndexingControllerTest { } @Test + public void verifyIndexRangeSearchIsCorrect_multipleAppCertificates() throws IOException { + InputStream inputStream = obtainDefaultIndexingMapForTest(); + + RuleIndexingController indexingController = new RuleIndexingController(inputStream); + + AppInstallMetadata appInstallMetadata = + new AppInstallMetadata.Builder() + .setPackageName("ddd") + .setAppCertificates(Arrays.asList("777", "999")) + .build(); + + List<RuleIndexRange> resultingIndexes = + indexingController.identifyRulesToEvaluate(appInstallMetadata); + + assertThat(resultingIndexes) + .containsExactly( + new RuleIndexRange(200, 300), + new RuleIndexRange(700, 800), + new RuleIndexRange(800, 900), + new RuleIndexRange(900, 945)); + } + + @Test public void verifyIndexRangeSearchIsCorrect_keysInFirstAndLastBlock() throws IOException { InputStream inputStream = obtainDefaultIndexingMapForTest(); @@ -71,7 +96,7 @@ public class RuleIndexingControllerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName("bbb") - .setAppCertificate("999") + .setAppCertificates(Collections.singletonList("999")) .build(); List<RuleIndexRange> resultingIndexes = @@ -93,7 +118,7 @@ public class RuleIndexingControllerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName("ccc") - .setAppCertificate("444") + .setAppCertificates(Collections.singletonList("444")) .build(); List<RuleIndexRange> resultingIndexes = @@ -125,7 +150,7 @@ public class RuleIndexingControllerTest { AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder() .setPackageName("ccc") - .setAppCertificate("444") + .setAppCertificates(Collections.singletonList("444")) .build(); List<RuleIndexRange> resultingIndexes = diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java index 05a9a80e262c..c0e7927a8d72 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java @@ -47,7 +47,7 @@ public final class ConversationInfoTest { .setContactPhoneNumber(PHONE_NUMBER) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) - .setVip(true) + .setImportant(true) .setNotificationSilenced(true) .setBubbled(true) .setDemoted(true) @@ -62,7 +62,7 @@ public final class ConversationInfoTest { assertEquals(PHONE_NUMBER, conversationInfo.getContactPhoneNumber()); assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId()); assertTrue(conversationInfo.isShortcutLongLived()); - assertTrue(conversationInfo.isVip()); + assertTrue(conversationInfo.isImportant()); assertTrue(conversationInfo.isNotificationSilenced()); assertTrue(conversationInfo.isBubbled()); assertTrue(conversationInfo.isDemoted()); @@ -83,7 +83,7 @@ public final class ConversationInfoTest { assertNull(conversationInfo.getContactPhoneNumber()); assertNull(conversationInfo.getNotificationChannelId()); assertFalse(conversationInfo.isShortcutLongLived()); - assertFalse(conversationInfo.isVip()); + assertFalse(conversationInfo.isImportant()); assertFalse(conversationInfo.isNotificationSilenced()); assertFalse(conversationInfo.isBubbled()); assertFalse(conversationInfo.isDemoted()); @@ -101,7 +101,7 @@ public final class ConversationInfoTest { .setContactPhoneNumber(PHONE_NUMBER) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) - .setVip(true) + .setImportant(true) .setNotificationSilenced(true) .setBubbled(true) .setPersonImportant(true) @@ -110,7 +110,7 @@ public final class ConversationInfoTest { .build(); ConversationInfo destination = new ConversationInfo.Builder(source) - .setVip(false) + .setImportant(false) .setContactStarred(false) .build(); @@ -120,7 +120,7 @@ public final class ConversationInfoTest { assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber()); assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId()); assertTrue(destination.isShortcutLongLived()); - assertFalse(destination.isVip()); + assertFalse(destination.isImportant()); assertTrue(destination.isNotificationSilenced()); assertTrue(destination.isBubbled()); assertTrue(destination.isPersonImportant()); diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java index bbcb54ef8d3e..03b5e38cadb7 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java @@ -21,16 +21,24 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import android.annotation.Nullable; +import android.content.Context; import android.content.LocusId; import android.content.pm.ShortcutInfo; import android.net.Uri; +import android.os.FileUtils; +import android.text.format.DateUtils; import android.util.ArraySet; +import androidx.test.InstrumentationRegistry; + +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.File; import java.util.Set; @RunWith(JUnit4.class) @@ -42,11 +50,34 @@ public final class ConversationStoreTest { private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890"); private static final String PHONE_NUMBER = "+1234567890"; + private static final String SHORTCUT_ID_2 = "ghi"; + private static final String NOTIFICATION_CHANNEL_ID_2 = "test : ghi"; + private static final LocusId LOCUS_ID_2 = new LocusId("jkl"); + private static final Uri CONTACT_URI_2 = Uri.parse("tel:+3234567890"); + private static final String PHONE_NUMBER_2 = "+3234567890"; + + private static final String SHORTCUT_ID_3 = "mno"; + private static final String NOTIFICATION_CHANNEL_ID_3 = "test : mno"; + private static final LocusId LOCUS_ID_3 = new LocusId("pqr"); + private static final Uri CONTACT_URI_3 = Uri.parse("tel:+9234567890"); + private static final String PHONE_NUMBER_3 = "+9234567890"; + + private MockScheduledExecutorService mMockScheduledExecutorService; + private TestContactQueryHelper mTestContactQueryHelper; private ConversationStore mConversationStore; + private File mFile; @Before public void setUp() { - mConversationStore = new ConversationStore(); + Context ctx = InstrumentationRegistry.getContext(); + mFile = new File(ctx.getCacheDir(), "testdir"); + mTestContactQueryHelper = new TestContactQueryHelper(ctx); + resetConversationStore(); + } + + @After + public void tearDown() { + FileUtils.deleteContentsAndDir(mFile); } @Test @@ -153,6 +184,130 @@ public final class ConversationStoreTest { mConversationStore.getConversationByNotificationChannelId(NOTIFICATION_CHANNEL_ID)); } + @Test + public void testDataPersistenceAndRestoration() { + // Add conversation infos, causing it to be loaded to disk. + ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2, + PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2); + ConversationInfo in3 = buildConversationInfo(SHORTCUT_ID_3, LOCUS_ID_3, CONTACT_URI_3, + PHONE_NUMBER_3, NOTIFICATION_CHANNEL_ID_3); + mConversationStore.addOrUpdate(in1); + mConversationStore.addOrUpdate(in2); + mConversationStore.addOrUpdate(in3); + + long futuresExecuted = mMockScheduledExecutorService.fastForwardTime( + 3L * DateUtils.MINUTE_IN_MILLIS); + assertEquals(1, futuresExecuted); + + mMockScheduledExecutorService.resetTimeElapsedMillis(); + + // During restoration, we want to confirm that this conversation was removed. + mConversationStore.deleteConversation(SHORTCUT_ID_3); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + + mTestContactQueryHelper.setQueryResult(true, true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2); + + resetConversationStore(); + ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID); + ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + ConversationInfo out3 = mConversationStore.getConversation(SHORTCUT_ID_3); + mConversationStore.deleteConversation(SHORTCUT_ID); + mConversationStore.deleteConversation(SHORTCUT_ID_2); + mConversationStore.deleteConversation(SHORTCUT_ID_3); + assertEquals(in1, out1); + assertEquals(in2, out2); + assertNull(out3); + } + + @Test + public void testDelayedDiskWrites() { + ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2, + PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2); + ConversationInfo in3 = buildConversationInfo(SHORTCUT_ID_3, LOCUS_ID_3, CONTACT_URI_3, + PHONE_NUMBER_3, NOTIFICATION_CHANNEL_ID_3); + + mConversationStore.addOrUpdate(in1); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + mMockScheduledExecutorService.resetTimeElapsedMillis(); + + // Should not see second conversation on disk because of disk write delay has not been + // reached. + mConversationStore.addOrUpdate(in2); + mMockScheduledExecutorService.fastForwardTime(DateUtils.MINUTE_IN_MILLIS); + + mTestContactQueryHelper.setQueryResult(true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER); + + resetConversationStore(); + ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID); + ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + assertEquals(in1, out1); + assertNull(out2); + + mConversationStore.addOrUpdate(in2); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + mMockScheduledExecutorService.resetTimeElapsedMillis(); + + mConversationStore.addOrUpdate(in3); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + + mTestContactQueryHelper.reset(); + mTestContactQueryHelper.setQueryResult(true, true, true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2, PHONE_NUMBER_3); + + resetConversationStore(); + out1 = mConversationStore.getConversation(SHORTCUT_ID); + out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + ConversationInfo out3 = mConversationStore.getConversation(SHORTCUT_ID_3); + assertEquals(in1, out1); + assertEquals(in2, out2); + assertEquals(in3, out3); + } + + @Test + public void testMimicDevicePowerOff() { + + // Even without fast forwarding time with our mock ScheduledExecutorService, we should + // see the conversations immediately saved to disk. + ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2, + PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2); + + mConversationStore.addOrUpdate(in1); + mConversationStore.addOrUpdate(in2); + mConversationStore.saveConversationsToDisk(); + + // Ensure that futures were cancelled and the immediate flush occurred. + assertEquals(0, mMockScheduledExecutorService.getFutures().size()); + + // Expect to see 2 executes: loadConversationFromDisk and saveConversationsToDisk. + // loadConversationFromDisk gets called each time we call #resetConversationStore(). + assertEquals(2, mMockScheduledExecutorService.getExecutes().size()); + + mTestContactQueryHelper.setQueryResult(true, true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2); + + resetConversationStore(); + ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID); + ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + assertEquals(in1, out1); + assertEquals(in2, out2); + } + + private void resetConversationStore() { + mFile.mkdir(); + mMockScheduledExecutorService = new MockScheduledExecutorService(); + mConversationStore = new ConversationStore(mFile, mMockScheduledExecutorService, + mTestContactQueryHelper); + mConversationStore.loadConversationsFromDisk(); + } + private static ConversationInfo buildConversationInfo(String shortcutId) { return buildConversationInfo(shortcutId, null, null, null, null); } @@ -167,8 +322,58 @@ public final class ConversationStoreTest { .setContactPhoneNumber(phoneNumber) .setNotificationChannelId(notificationChannelId) .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED) - .setVip(true) + .setImportant(true) .setBubbled(true) .build(); } + + private static class TestContactQueryHelper extends ContactsQueryHelper { + + private int mQueryCalls; + private boolean[] mQueryResult; + + private int mPhoneNumberCalls; + private String[] mPhoneNumberResult; + + TestContactQueryHelper(Context context) { + super(context); + + mQueryCalls = 0; + mPhoneNumberCalls = 0; + } + + private void setQueryResult(boolean... values) { + mQueryResult = values; + } + + private void setPhoneNumberResult(String... values) { + mPhoneNumberResult = values; + } + + private void reset() { + mQueryCalls = 0; + mQueryResult = null; + mPhoneNumberCalls = 0; + mPhoneNumberResult = null; + } + + @Override + boolean query(String contactUri) { + if (mQueryResult != null && mQueryCalls < mQueryResult.length) { + return mQueryResult[mQueryCalls++]; + } + mQueryCalls++; + return false; + } + + @Override + @Nullable + String getPhoneNumber() { + if (mPhoneNumberResult != null && mPhoneNumberCalls < mPhoneNumberResult.length) { + return mPhoneNumberResult[mPhoneNumberCalls++]; + } + mPhoneNumberCalls++; + return null; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index ad5c57dd11bc..498d8886eec3 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -16,10 +16,15 @@ package com.android.server.people.data; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_ADDED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; +import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; + import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -33,6 +38,8 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.app.Person; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; @@ -88,6 +95,7 @@ public final class DataManagerTest { private static final String TEST_SHORTCUT_ID = "sc"; private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123"; private static final String PHONE_NUMBER = "+1234567890"; + private static final String NOTIFICATION_CHANNEL_ID = "test : sc"; private static final long MILLIS_PER_MINUTE = 1000L * 60L; @Mock private Context mContext; @@ -103,6 +111,7 @@ public final class DataManagerTest { @Mock private StatusBarNotification mStatusBarNotification; @Mock private Notification mNotification; + private NotificationChannel mNotificationChannel; private DataManager mDataManager; private int mCallingUserId; private TestInjector mInjector; @@ -152,6 +161,10 @@ public final class DataManagerTest { when(mStatusBarNotification.getUser()).thenReturn(UserHandle.of(USER_ID_PRIMARY)); when(mNotification.getShortcutId()).thenReturn(TEST_SHORTCUT_ID); + mNotificationChannel = new NotificationChannel( + NOTIFICATION_CHANNEL_ID, "test channel", NotificationManager.IMPORTANCE_DEFAULT); + mNotificationChannel.setConversationId("test", TEST_SHORTCUT_ID); + mCallingUserId = USER_ID_PRIMARY; mInjector = new TestInjector(); @@ -284,9 +297,8 @@ public final class DataManagerTest { } @Test - public void testNotificationListener() { + public void testNotificationOpened() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); - mDataManager.onUserUnlocked(USER_ID_SECONDARY); ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, buildPerson()); @@ -308,6 +320,83 @@ public final class DataManagerTest { } @Test + public void testNotificationChannelCreated() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), + mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_ADDED); + + ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY) + .getConversationStore() + .getConversation(TEST_SHORTCUT_ID); + assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId()); + assertFalse(conversationInfo.isImportant()); + assertFalse(conversationInfo.isNotificationSilenced()); + assertFalse(conversationInfo.isDemoted()); + } + + @Test + public void testNotificationChannelModified() { + mNotificationChannel.setImportantConversation(true); + + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), + mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY) + .getConversationStore() + .getConversation(TEST_SHORTCUT_ID); + assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId()); + assertTrue(conversationInfo.isImportant()); + assertFalse(conversationInfo.isNotificationSilenced()); + assertFalse(conversationInfo.isDemoted()); + } + + @Test + public void testNotificationChannelDeleted() { + mDataManager.onUserUnlocked(USER_ID_PRIMARY); + mDataManager.onUserUnlocked(USER_ID_SECONDARY); + + ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID, + buildPerson()); + mDataManager.onShortcutAddedOrUpdated(shortcut); + + NotificationListenerService listenerService = + mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY); + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), + mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_ADDED); + ConversationInfo conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY) + .getConversationStore() + .getConversation(TEST_SHORTCUT_ID); + assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId()); + + listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY), + mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + conversationInfo = mDataManager.getPackage(TEST_PKG_NAME, USER_ID_PRIMARY) + .getConversationStore() + .getConversation(TEST_SHORTCUT_ID); + assertNull(conversationInfo.getNotificationChannelId()); + assertFalse(conversationInfo.isImportant()); + assertFalse(conversationInfo.isNotificationSilenced()); + assertFalse(conversationInfo.isDemoted()); + } + + @Test public void testCallLogContentObserver() { mDataManager.onUserUnlocked(USER_ID_PRIMARY); mDataManager.onUserUnlocked(USER_ID_SECONDARY); diff --git a/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java b/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java new file mode 100644 index 000000000000..8b8ba1247a4b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java @@ -0,0 +1,238 @@ +/* + * 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 com.android.internal.util.Preconditions; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Mock implementation of ScheduledExecutorService for testing. All commands will run + * synchronously. Commands passed to {@link #submit(Runnable)} and {@link #execute(Runnable)} will + * run immediately. Commands scheduled via {@link #schedule(Runnable, long, TimeUnit)} will run + * after calling {@link #fastForwardTime(long)}. + */ +class MockScheduledExecutorService implements ScheduledExecutorService { + + private final List<Runnable> mExecutes = new ArrayList<>(); + private final List<MockScheduledFuture<?>> mFutures = new ArrayList<>(); + private long mTimeElapsedMillis = 0; + + /** + * Advances fake time, runs all the commands for which the delay has expired. + */ + long fastForwardTime(long millis) { + mTimeElapsedMillis += millis; + ImmutableList<MockScheduledFuture<?>> futuresCopy = ImmutableList.copyOf(mFutures); + mFutures.clear(); + long totalExecuted = 0; + for (MockScheduledFuture<?> future : futuresCopy) { + if (future.getDelay() < mTimeElapsedMillis) { + future.getCommand().run(); + mExecutes.add(future.getCommand()); + totalExecuted += 1; + } else { + mFutures.add(future); + } + } + return totalExecuted; + } + + List<Runnable> getExecutes() { + return mExecutes; + } + + List<MockScheduledFuture<?>> getFutures() { + return mFutures; + } + + void resetTimeElapsedMillis() { + mTimeElapsedMillis = 0; + } + + /** + * Fakes a schedule execution of {@link Runnable}. The command will be executed by an explicit + * call to {@link #fastForwardTime(long)}. + */ + @Override + public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { + Preconditions.checkState(unit == TimeUnit.MILLISECONDS); + MockScheduledFuture<?> future = new MockScheduledFuture<>(command, delay, unit); + mFutures.add(future); + return future; + } + + @Override + public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, + TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, + long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public void shutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public List<Runnable> shutdownNow() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Callable<T> task) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Runnable task, T result) { + throw new UnsupportedOperationException(); + } + + @Override + public Future<?> submit(Runnable command) { + mExecutes.add(command); + MockScheduledFuture<?> future = new MockScheduledFuture<>(command, 0, + TimeUnit.MILLISECONDS); + future.getCommand().run(); + return future; + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) + throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, + TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks) + throws ExecutionException, InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) + throws ExecutionException, InterruptedException, TimeoutException { + throw new UnsupportedOperationException(); + } + + @Override + public void execute(Runnable command) { + mExecutes.add(command); + command.run(); + } + + class MockScheduledFuture<V> implements ScheduledFuture<V> { + + private final Runnable mCommand; + private final long mDelay; + private boolean mCancelled = false; + + MockScheduledFuture(Runnable command, long delay, TimeUnit timeUnit) { + mCommand = command; + mDelay = delay; + } + + public long getDelay() { + return mDelay; + } + + public Runnable getCommand() { + return mCommand; + } + + @Override + public long getDelay(TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public int compareTo(Delayed o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + mCancelled = true; + return mFutures.remove(this); + } + + @Override + public boolean isCancelled() { + return mCancelled; + } + + @Override + public boolean isDone() { + return !mFutures.contains(this); + } + + @Override + public V get() throws ExecutionException, InterruptedException { + return null; + } + + @Override + public V get(long timeout, TimeUnit unit) + throws ExecutionException, InterruptedException, TimeoutException { + return null; + } + } +} 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 index ec4789ad0cdf..1ddc21e4ea4d 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java @@ -20,15 +20,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.content.LocusId; import android.content.pm.ShortcutInfo; import android.net.Uri; +import androidx.test.InstrumentationRegistry; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.File; import java.util.List; @RunWith(JUnit4.class) @@ -52,8 +56,12 @@ public final class PackageDataTest { @Before public void setUp() { + Context ctx = InstrumentationRegistry.getContext(); + File testDir = new File(ctx.getCacheDir(), "testdir"); + testDir.mkdir(); mPackageData = new PackageData( - PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp); + PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp, + new MockScheduledExecutorService(), testDir, new ContactsQueryHelper(ctx)); ConversationInfo conversationInfo = new ConversationInfo.Builder() .setShortcutId(SHORTCUT_ID) .setLocusId(LOCUS_ID) diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java index e4248a04878d..b273578ada3a 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java @@ -29,8 +29,11 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; +import android.content.Context; import android.content.LocusId; +import androidx.test.InstrumentationRegistry; + import com.android.server.LocalServices; import org.junit.After; @@ -41,10 +44,12 @@ import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Predicate; @RunWith(JUnit4.class) @@ -69,7 +74,13 @@ public final class UsageStatsQueryHelperTest { addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal); - mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false); + Context ctx = InstrumentationRegistry.getContext(); + File testDir = new File(ctx.getCacheDir(), "testdir"); + ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService(); + ContactsQueryHelper helper = new ContactsQueryHelper(ctx); + + mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false, + scheduledExecutorService, testDir, helper); mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder() .setShortcutId(SHORTCUT_ID) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) @@ -175,7 +186,7 @@ public final class UsageStatsQueryHelperTest { assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2)); } - private void addUsageEvents(UsageEvents.Event ... events) { + private void addUsageEvents(UsageEvents.Event... events) { UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{}); when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(), anyBoolean(), anyBoolean())).thenReturn(usageEvents); @@ -228,6 +239,12 @@ public final class UsageStatsQueryHelperTest { private ConversationInfo mConversationInfo; + TestConversationStore(File packageDir, + ScheduledExecutorService scheduledExecutorService, + ContactsQueryHelper helper) { + super(packageDir, scheduledExecutorService, helper); + } + @Override @Nullable ConversationInfo getConversation(@Nullable String shortcutId) { @@ -237,13 +254,18 @@ public final class UsageStatsQueryHelperTest { private static class TestPackageData extends PackageData { - private final TestConversationStore mConversationStore = new TestConversationStore(); + private final TestConversationStore mConversationStore; private final TestEventStore mEventStore = new TestEventStore(); TestPackageData(@NonNull String packageName, @UserIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, - @NonNull Predicate<String> isDefaultSmsAppPredicate) { - super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate); + @NonNull Predicate<String> isDefaultSmsAppPredicate, + @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir, + @NonNull ContactsQueryHelper helper) { + super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate, + scheduledExecutorService, rootDir, helper); + mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService, + helper); } @Override diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 3e3f40d31d0e..e28d13a1a20c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -35,7 +35,6 @@ import android.content.pm.parsing.ComponentParseUtils.ParsedActivity; import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo; import android.content.pm.parsing.PackageImpl; import android.content.pm.parsing.ParsingPackage; -import android.net.Uri; import android.os.Build; import android.os.Process; import android.util.ArrayMap; @@ -87,6 +86,17 @@ public class AppsFilterTest { return pkg; } + private static ParsingPackage pkgQueriesProvider(String packageName, + String... queriesAuthorities) { + ParsingPackage pkg = pkg(packageName); + if (queriesAuthorities != null) { + for (String authority : queriesAuthorities) { + pkg.addQueriesProvider(authority); + } + } + return pkg; + } + private static ParsingPackage pkg(String packageName, String... queriesPackages) { ParsingPackage pkg = pkg(packageName); if (queriesPackages != null) { @@ -172,8 +182,7 @@ public class AppsFilterTest { PackageSetting target = simulateAddPackage(appsFilter, pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", - new Intent().setData(Uri.parse("content://com.some.authority"))), + pkgQueriesProvider("com.some.other.package", "com.some.authority"), DUMMY_CALLING_UID); assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); @@ -188,8 +197,7 @@ public class AppsFilterTest { PackageSetting target = simulateAddPackage(appsFilter, pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", - new Intent().setData(Uri.parse("content://com.some.other.authority"))), + pkgQueriesProvider("com.some.other.package", "com.some.other.authority"), DUMMY_CALLING_UID); assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); @@ -205,8 +213,7 @@ public class AppsFilterTest { pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, - pkg("com.some.other.package", - new Intent().setData(Uri.parse("content://com.some.authority"))), + pkgQueriesProvider("com.some.other.package", "com.some.authority"), DUMMY_CALLING_UID); assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0)); @@ -266,7 +273,7 @@ public class AppsFilterTest { appsFilter.onSystemReady(); PackageSetting target = simulateAddPackage(appsFilter, - pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID); + pkg("com.some.package").setForceQueryable(true), DUMMY_TARGET_UID); PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package"), DUMMY_CALLING_UID); diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 3d190be8888b..77f842aa503f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -556,6 +556,11 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { void injectRestoreCallingIdentity(long token) { mInjectedCallingUid = (int) token; } + + @Override + boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) { + return true; + } } protected class LauncherAppsTestable extends LauncherApps { @@ -1617,6 +1622,22 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } /** + * Make a long lived shortcut with an ID. + */ + protected ShortcutInfo makeLongLivedShortcut(String id) { + final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, id) + .setActivity(new ComponentName(mClientContext.getPackageName(), "main")) + .setShortLabel("title-" + id) + .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class)) + .setLongLived(true); + final ShortcutInfo s = b.build(); + + s.setTimestamp(mInjectedCurrentTimeMillis); // HACK + + return s; + } + + /** * Make an intent. */ protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 63da5fbab122..f03670843f3a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -1240,7 +1240,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( - makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); + makeLongLivedShortcut("s1"), makeLongLivedShortcut("s2"), makeShortcut("s3")))); }); // Pin 2 and 3 @@ -1250,9 +1250,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); // Cache 1 and 2 + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), + HANDLE_USER_0); + }); + setCaller(CALLING_PACKAGE_1); - getCallerShortcut("s1").setCached(); - getCallerShortcut("s2").setCached(); // Get manifest shortcuts assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_MANIFEST), @@ -1315,8 +1318,9 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { public void testCachedShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeShortcut("s2"), - makeShortcut("s3"), makeShortcut("s4")))); + assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), + makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"), + makeLongLivedShortcut("s4")))); }); // Pin s2 @@ -1325,11 +1329,13 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { HANDLE_USER_0); }); - // Cache 2, 3 and 4 + // Cache some, but non long lived shortcuts will be ignored. + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s4"), + HANDLE_USER_0); + }); + setCaller(CALLING_PACKAGE_1); - getCallerShortcut("s2").setCached(); - getCallerShortcut("s3").setCached(); - getCallerShortcut("s4").setCached(); // Get dynamic shortcuts assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), @@ -1339,27 +1345,37 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { "s2"); // Get cached shortcuts assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), - "s2", "s3", "s4"); + "s2", "s4"); // Remove a dynamic cached shortcut - mManager.removeDynamicShortcuts(list("s3")); + mManager.removeDynamicShortcuts(list("s4")); assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), - "s1", "s2", "s4"); + "s1", "s2", "s3"); assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), - "s2", "s3", "s4"); + "s2", "s4"); - // Remove dynamic cached long lived shortcuts - mManager.removeLongLivedShortcuts(list("s3", "s4")); - assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), - "s1", "s2"); + // uncache a non-dynamic shortcut. Should be removed. + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s4"), + HANDLE_USER_0); + }); assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s2"); + // Cache another shortcut + runWithCaller(LAUNCHER_1, USER_0, () -> { + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s3"), + HANDLE_USER_0); + }); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), + "s2", "s3"); + // Remove a dynamic cached pinned long lived shortcut mManager.removeLongLivedShortcuts(list("s2")); assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC), - "s1"); - assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED)); + "s1", "s3"); + assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), + "s3"); assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_PINNED), "s2"); } @@ -1371,7 +1387,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // Set up shortcuts. setCaller(CALLING_PACKAGE_1); - final ShortcutInfo s1_1 = makeShortcut("s1"); + final ShortcutInfo s1_1 = makeLongLivedShortcut("s1"); final ShortcutInfo s1_2 = makeShortcutWithLocusId("s2", makeLocusId("l1")); assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2))); @@ -1395,6 +1411,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { setCaller(CALLING_PACKAGE_3); final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2")); + s3_2.setLongLived(); + assertTrue(mManager.setDynamicShortcuts(list(s3_2))); getCallerShortcut("s3").setTimestamp(START_TIME + 5000); @@ -1535,26 +1553,20 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO More tests: pinned but dynamic. - // Cache some shortcuts - setCaller(CALLING_PACKAGE_1); - getCallerShortcut("s1").setCached(); - - setCaller(CALLING_PACKAGE_2); - getCallerShortcut("s4").setCached(); - - setCaller(CALLING_PACKAGE_3); - getCallerShortcut("s3").setCached(); - setCaller(LAUNCHER_1); + // Cache some shortcuts. Only long lived shortcuts can get cached. + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"), getCallingUser()); + mLauncherApps.cacheShortcuts(CALLING_PACKAGE_3, list("s3"), getCallingUser()); + // Cached ones only assertShortcutIds(assertAllNotKeyFieldsOnly( mLauncherApps.getShortcuts(buildQuery( - /* time =*/ 0, CALLING_PACKAGE_2, + /* time =*/ 0, CALLING_PACKAGE_3, /* activity =*/ null, ShortcutQuery.FLAG_MATCH_CACHED), getCallingUser())), - "s4"); + "s3"); // All packages. assertShortcutIds(assertAllNotKeyFieldsOnly( @@ -1563,7 +1575,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /* activity =*/ null, ShortcutQuery.FLAG_MATCH_CACHED), getCallingUser())), - "s1", "s4", "s3"); + "s1", "s3"); assertExpectException( IllegalArgumentException.class, "package name must also be set", () -> { @@ -1581,7 +1593,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /* activity =*/ null, ShortcutQuery.FLAG_MATCH_CACHED), getCallingUser())), - "s1", "s4", "s3"); + "s1", "s3"); } public void testGetShortcuts_shortcutKinds() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java index 804c1b9e09fd..1a6c6b4011cd 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -224,21 +224,43 @@ public class RollbackUnitTest { } @Test - public void snapshotThenDelete() { + public void snapshotThenDeleteNoApex() { + Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); + PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); + PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, false); + rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); + + int[] userIds = {111, 222}; + rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper); + + verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds)); + + rollback.delete(mMockDataHelper); + + verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(111)); + verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(222)); + verify(mMockDataHelper, never()).destroyApexDeSnapshots(anyInt()); + + assertThat(rollback.isDeleted()).isTrue(); + } + + @Test + public void snapshotThenDeleteWithApex() { Rollback rollback = new Rollback(123, new File("/test/testing"), -1, USER, INSTALLER); PackageRollbackInfo pkgInfo1 = newPkgInfoFor(PKG_1, 12, 10, false); PackageRollbackInfo pkgInfo2 = newPkgInfoFor(PKG_2, 18, 12, true); rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2)); - int[] userIds = {12, 18}; + int[] userIds = {111, 222}; rollback.snapshotUserData(PKG_2, userIds, mMockDataHelper); verify(mMockDataHelper).snapshotAppData(eq(123), pkgRollbackInfoFor(PKG_2), eq(userIds)); rollback.delete(mMockDataHelper); - verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(12)); - verify(mMockDataHelper).destroyAppDataSnapshot(eq(123), pkgRollbackInfoFor(PKG_2), eq(18)); + verify(mMockDataHelper, never()) + .destroyAppDataSnapshot(anyInt(), pkgRollbackInfoFor(PKG_2), anyInt()); + verify(mMockDataHelper).destroyApexDeSnapshots(123); assertThat(rollback.isDeleted()).isTrue(); } diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index fde0ddffa365..4d0481be20b3 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -144,6 +144,40 @@ public class SystemConfigTest { assertEquals("Incorrect blacklist", expectedBlack, actualBlack); } + @Test + public void testComponentOverride() throws Exception { + final String contents = + "<permissions>" + + " <component-override package=\"com.android.package1\">\n" + + " <component class=\"com.android.package1.Full\" enabled=\"true\"/>" + + " <component class=\".Relative\" enabled=\"false\" />\n" + + " </component-override>" + + " <component-override package=\"com.android.package2\" >\n" + + " <component class=\"com.android.package3.Relative2\" enabled=\"yes\" />\n" + + " </component-override>\n" + + "</permissions>"; + + final File folder = createTempSubfolder("folder"); + createTempFile(folder, "component-override.xml", contents); + + mSysConfig.readPermissions(folder, /* No permission needed anyway */ 0); + + final ArrayMap<String, Boolean> packageOneExpected = new ArrayMap<>(); + packageOneExpected.put("com.android.package1.Full", true); + packageOneExpected.put("com.android.package1.Relative", false); + + final ArrayMap<String, Boolean> packageTwoExpected = new ArrayMap<>(); + packageTwoExpected.put("com.android.package3.Relative2", true); + + final Map<String, Boolean> packageOne = mSysConfig.getComponentsEnabledStates( + "com.android.package1"); + assertEquals(packageOneExpected, packageOne); + + final Map<String, Boolean> packageTwo = mSysConfig.getComponentsEnabledStates( + "com.android.package2"); + assertEquals(packageTwoExpected, packageTwo); + } + /** * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents. * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index f608babd062c..3f0cda3b8e5a 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -47,7 +47,6 @@ android_test { "libbacktrace", "libbase", "libbinder", - "libbinderthreadstate", "libc++", "libcutils", "liblog", diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java index 5b5ad877081c..cbb760a7a871 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java @@ -44,6 +44,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.internal.matchers.Not; import java.io.File; import java.util.ArrayList; @@ -239,6 +240,52 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { verify(af2, never()).openRead(); } + @Test + public void testRemoveNotificationRunnable() throws Exception { + NotificationHistory nh = mock(NotificationHistory.class); + NotificationHistoryDatabase.RemoveNotificationRunnable rnr = + mDataBase.new RemoveNotificationRunnable("pkg", 123); + rnr.setNotificationHistory(nh); + + AtomicFile af = mock(AtomicFile.class); + when(af.getBaseFile()).thenReturn(new File(mRootDir, "af")); + mDataBase.mHistoryFiles.addLast(af); + + when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(true); + + mDataBase.mBuffer = mock(NotificationHistory.class); + + rnr.run(); + + verify(mDataBase.mBuffer).removeNotificationFromWrite("pkg", 123); + verify(af).openRead(); + verify(nh).removeNotificationFromWrite("pkg", 123); + verify(af).startWrite(); + } + + @Test + public void testRemoveNotificationRunnable_noChanges() throws Exception { + NotificationHistory nh = mock(NotificationHistory.class); + NotificationHistoryDatabase.RemoveNotificationRunnable rnr = + mDataBase.new RemoveNotificationRunnable("pkg", 123); + rnr.setNotificationHistory(nh); + + AtomicFile af = mock(AtomicFile.class); + when(af.getBaseFile()).thenReturn(new File(mRootDir, "af")); + mDataBase.mHistoryFiles.addLast(af); + + when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(false); + + mDataBase.mBuffer = mock(NotificationHistory.class); + + rnr.run(); + + verify(mDataBase.mBuffer).removeNotificationFromWrite("pkg", 123); + verify(af).openRead(); + verify(nh).removeNotificationFromWrite("pkg", 123); + verify(af, never()).startWrite(); + } + private class TestFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider { public Map<File, Long> creationDates = new HashMap<>(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java index b5eb1bf31848..2c548be185c9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java @@ -289,6 +289,20 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase { } @Test + public void testDeleteNotificationHistoryItem_userUnlocked() { + String pkg = "pkg"; + long time = 235; + NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class); + + mHistoryManager.onUserUnlocked(USER_SYSTEM); + mHistoryManager.replaceNotificationHistoryDatabase(USER_SYSTEM, userHistory); + + mHistoryManager.deleteNotificationHistoryItem(pkg, 1, time); + + verify(userHistory, times(1)).deleteNotificationHistoryItem(pkg, time); + } + + @Test public void testTriggerWriteToDisk() { NotificationHistoryDatabase userHistorySystem = mock(NotificationHistoryDatabase.class); NotificationHistoryDatabase userHistoryAll = mock(NotificationHistoryDatabase.class); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index e0ee3ce3aa57..57428dc75813 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -32,7 +32,6 @@ import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS; -import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; @@ -42,6 +41,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -50,6 +51,8 @@ import static android.os.Build.VERSION_CODES.P; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; +import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; @@ -128,6 +131,7 @@ import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; import android.service.notification.Adjustment; +import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; @@ -1144,7 +1148,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); assertEquals(1, mNotificationRecordLogger.getCalls().size()); NotificationRecordLoggerFake.CallRecord call = mNotificationRecordLogger.get(0); - assertTrue(call.shouldLog()); + assertTrue(call.shouldLog); + assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + call.event); assertNotNull(call.r); assertNull(call.old); assertEquals(0, call.position); @@ -1153,7 +1159,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(0, call.r.getSbn().getId()); assertEquals(tag, call.r.getSbn().getTag()); assertNotNull(call.r.getSbn().getInstanceId()); - assertEquals(0, call.r.getSbn().getInstanceId().getId()); + assertEquals(0, call.getInstanceId()); // Fake instance IDs are assigned in order } @Test @@ -1171,18 +1177,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); assertEquals(2, mNotificationRecordLogger.getCalls().size()); - assertTrue(mNotificationRecordLogger.get(0).shouldLog()); + assertTrue(mNotificationRecordLogger.get(0).shouldLog); assertEquals( - NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED, - mNotificationRecordLogger.get(0).getUiEvent()); - assertEquals(0, mNotificationRecordLogger.get(0).r.getSbn().getInstanceId().getId()); + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + mNotificationRecordLogger.get(0).event); + assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); - assertTrue(mNotificationRecordLogger.get(1).shouldLog()); + assertTrue(mNotificationRecordLogger.get(1).shouldLog); assertEquals( - NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_UPDATED, - mNotificationRecordLogger.get(1).getUiEvent()); + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED, + mNotificationRecordLogger.get(1).event); // Instance ID doesn't change on update of an active notification - assertEquals(0, mNotificationRecordLogger.get(1).r.getSbn().getInstanceId().getId()); + assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId()); } @Test @@ -1194,8 +1200,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { generateNotificationRecord(null).getNotification(), 0); waitForIdle(); assertEquals(2, mNotificationRecordLogger.getCalls().size()); - assertTrue(mNotificationRecordLogger.get(0).shouldLog()); - assertFalse(mNotificationRecordLogger.get(1).shouldLog()); + assertTrue(mNotificationRecordLogger.get(0).shouldLog); + assertFalse(mNotificationRecordLogger.get(1).shouldLog); } @Test @@ -1210,20 +1216,37 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); mBinderService.enqueueNotificationWithTag(PKG, PKG, tag, 0, notification, 0); waitForIdle(); - assertEquals(2, mNotificationRecordLogger.getCalls().size()); + assertEquals(3, mNotificationRecordLogger.getCalls().size()); + + assertEquals( + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + mNotificationRecordLogger.get(0).event); + assertTrue(mNotificationRecordLogger.get(0).shouldLog); + assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); - assertTrue(mNotificationRecordLogger.get(0).shouldLog()); + assertEquals(REASON_APP_CANCEL, mNotificationRecordLogger.get(1).reason); assertEquals( - NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED, - mNotificationRecordLogger.get(0).getUiEvent()); - assertEquals(0, mNotificationRecordLogger.get(0).r.getSbn().getInstanceId().getId()); + NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_APP_CANCEL, + mNotificationRecordLogger.get(1).event); + assertTrue(mNotificationRecordLogger.get(1).shouldLog); + assertEquals(0, mNotificationRecordLogger.get(1).getInstanceId()); - assertTrue(mNotificationRecordLogger.get(1).shouldLog()); assertEquals( - NotificationRecordLogger.NotificationReportedEvents.NOTIFICATION_POSTED, - mNotificationRecordLogger.get(1).getUiEvent()); + NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED, + mNotificationRecordLogger.get(2).event); + assertTrue(mNotificationRecordLogger.get(2).shouldLog); // New instance ID because notification was canceled before re-post - assertEquals(1, mNotificationRecordLogger.get(1).r.getSbn().getInstanceId().getId()); + assertEquals(1, mNotificationRecordLogger.get(2).getInstanceId()); + } + + @Test + public void testCancelNonexistentNotification() throws Exception { + mBinderService.cancelNotificationWithTag(PKG, PKG, + "testCancelNonexistentNotification", 0, 0); + waitForIdle(); + // The notification record logger doesn't even get called when a nonexistent notification + // is cancelled, because that happens very frequently and is not interesting. + assertEquals(0, mNotificationRecordLogger.getCalls().size()); } @Test @@ -3365,6 +3388,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); assertEquals(NotificationStats.DISMISSAL_AOD, r.getStats().getDismissalSurface()); + + // Using mService.addNotification() does not generate a NotificationRecordLogger log, + // so we only get the cancel notification. + assertEquals(1, mNotificationRecordLogger.getCalls().size()); + + assertEquals(REASON_CANCEL, mNotificationRecordLogger.get(0).reason); + assertEquals( + NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_AOD, + mNotificationRecordLogger.get(0).event); + assertTrue(mNotificationRecordLogger.get(0).shouldLog); + assertEquals(0, mNotificationRecordLogger.get(0).getInstanceId()); } @Test @@ -6273,4 +6307,60 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(PRIORITY_CATEGORY_CALLS, actual); } + + @Test + public void testGetConversationsForPackage_hasShortcut() throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); + ArrayList<ConversationChannelWrapper> convos = new ArrayList<>(); + ConversationChannelWrapper convo1 = new ConversationChannelWrapper(); + NotificationChannel channel1 = new NotificationChannel("a", "a", 1); + channel1.setConversationId("parent1", "convo 1"); + convo1.setNotificationChannel(channel1); + convos.add(convo1); + + ConversationChannelWrapper convo2 = new ConversationChannelWrapper(); + NotificationChannel channel2 = new NotificationChannel("b", "b", 1); + channel2.setConversationId("parent1", "convo 2"); + convo2.setNotificationChannel(channel2); + convos.add(convo2); + when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos); + + // only one valid shortcut + LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery() + .setPackage(PKG_P) + .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED) + .setShortcutIds(Arrays.asList(channel1.getConversationId())); + ShortcutInfo si = mock(ShortcutInfo.class); + when(si.getShortLabel()).thenReturn("Hello"); + when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si)); + + List<ConversationChannelWrapper> conversations = + mBinderService.getConversationsForPackage(PKG_P, mUid).getList(); + assertEquals(si, conversations.get(0).getShortcutInfo()); + assertEquals(si, conversations.get(1).getShortcutInfo()); + + } + + @Test + public void testGetConversationsForPackage_doesNotHaveShortcut() throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); + ArrayList<ConversationChannelWrapper> convos = new ArrayList<>(); + ConversationChannelWrapper convo1 = new ConversationChannelWrapper(); + NotificationChannel channel1 = new NotificationChannel("a", "a", 1); + channel1.setConversationId("parent1", "convo 1"); + convo1.setNotificationChannel(channel1); + convos.add(convo1); + + ConversationChannelWrapper convo2 = new ConversationChannelWrapper(); + NotificationChannel channel2 = new NotificationChannel("b", "b", 1); + channel2.setConversationId("parent1", "convo 2"); + convo2.setNotificationChannel(channel2); + convos.add(convo2); + when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos); + + List<ConversationChannelWrapper> conversations = + mBinderService.getConversationsForPackage(PKG_P, mUid).getList(); + assertNull(conversations.get(0).getShortcutInfo()); + assertNull(conversations.get(1).getShortcutInfo()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java index 5c1487f0fdf6..b120dbee03c5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java @@ -16,6 +16,8 @@ package com.android.server.notification; +import com.android.internal.logging.UiEventLogger; + import java.util.ArrayList; import java.util.List; @@ -24,18 +26,27 @@ import java.util.List; */ class NotificationRecordLoggerFake implements NotificationRecordLogger { static class CallRecord extends NotificationRecordPair { - public int position, buzzBeepBlink; + static final int INVALID = -1; + public int position = INVALID, buzzBeepBlink = INVALID, reason = INVALID; + public boolean shouldLog; + public UiEventLogger.UiEventEnum event; CallRecord(NotificationRecord r, NotificationRecord old, int position, int buzzBeepBlink) { super(r, old); + this.position = position; this.buzzBeepBlink = buzzBeepBlink; + shouldLog = shouldLog(buzzBeepBlink); + event = NotificationReportedEvent.fromRecordPair(this); } - boolean shouldLog() { - return shouldLog(buzzBeepBlink); + CallRecord(NotificationRecord r, int reason, int dismissalSurface) { + super(r, null); + this.reason = reason; + shouldLog = true; + event = NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface); } } - private List<CallRecord> mCalls = new ArrayList<CallRecord>(); + private List<CallRecord> mCalls = new ArrayList<>(); List<CallRecord> getCalls() { return mCalls; @@ -50,4 +61,10 @@ class NotificationRecordLoggerFake implements NotificationRecordLogger { int position, int buzzBeepBlink) { mCalls.add(new CallRecord(r, old, position, buzzBeepBlink)); } + + @Override + public void logNotificationCancelled(NotificationRecord r, int reason, int dismissalSurface) { + mCalls.add(new CallRecord(r, reason, dismissalSurface)); + } + } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index bb84b04361ea..3f690b105d8b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -26,6 +26,8 @@ import static android.util.FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertNull; import static junit.framework.Assert.fail; @@ -64,6 +66,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.Secure; +import android.service.notification.ConversationChannelWrapper; import android.test.suitebuilder.annotation.SmallTest; import android.testing.TestableContentResolver; import android.util.ArrayMap; @@ -91,6 +94,7 @@ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -2939,4 +2943,97 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertNotNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true)); } + + @Test + public void testGetConversations_invalidPkg() { + assertThat(mHelper.getConversations("bad", 1)).isEmpty(); + } + + @Test + public void testGetConversations_noConversations() { + NotificationChannel channel = + new NotificationChannel("not_convo", "not_convo", IMPORTANCE_DEFAULT); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + + assertThat(mHelper.getConversations(PKG_O, UID_O)).isEmpty(); + } + + @Test + public void testGetConversations_noDisabledGroups() { + NotificationChannelGroup group = new NotificationChannelGroup("a", "a"); + group.setBlocked(true); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + NotificationChannel parent = new NotificationChannel("parent", "p", 1); + mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); + + NotificationChannel channel = + new NotificationChannel("convo", "convo", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "convo"); + channel.setGroup(group.getId()); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + + assertThat(mHelper.getConversations(PKG_O, UID_O)).isEmpty(); + } + + @Test + public void testGetConversations_noDeleted() { + NotificationChannel parent = new NotificationChannel("parent", "p", 1); + mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); + NotificationChannel channel = + new NotificationChannel("convo", "convo", IMPORTANCE_DEFAULT); + channel.setConversationId("parent", "convo"); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + mHelper.deleteNotificationChannel(PKG_O, UID_O, channel.getId()); + + assertThat(mHelper.getConversations(PKG_O, UID_O)).isEmpty(); + } + + @Test + public void testGetConversations() { + NotificationChannelGroup group = new NotificationChannelGroup("acct", "account_name"); + mHelper.createNotificationChannelGroup(PKG_O, UID_O, group, true); + + NotificationChannel messages = + new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT); + messages.setGroup(group.getId()); + mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false); + NotificationChannel calls = + new NotificationChannel("calls", "Calls", IMPORTANCE_HIGH); + mHelper.createNotificationChannel(PKG_O, UID_O, calls, true, false); + + NotificationChannel channel = + new NotificationChannel("A person", "A lovely person", IMPORTANCE_DEFAULT); + channel.setGroup(group.getId()); + channel.setConversationId(messages.getId(), channel.getName().toString()); + mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false); + + NotificationChannel channel2 = + new NotificationChannel("B person", "B fabulous person", IMPORTANCE_DEFAULT); + channel2.setConversationId(calls.getId(), channel.getName().toString()); + mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false); + + Map<String, NotificationChannel> expected = new HashMap<>(); + expected.put(channel.getId(), channel); + expected.put(channel2.getId(), channel2); + + Map<String, CharSequence> expectedGroup = new HashMap<>(); + expectedGroup.put(channel.getId(), group.getName()); + expectedGroup.put(channel2.getId(), null); + + Map<String, CharSequence> expectedParentLabel= new HashMap<>(); + expectedParentLabel.put(channel.getId(), messages.getName()); + expectedParentLabel.put(channel2.getId(), calls.getName()); + + ArrayList<ConversationChannelWrapper> convos = mHelper.getConversations(PKG_O, UID_O); + assertThat(convos).hasSize(2); + + for (ConversationChannelWrapper convo : convos) { + assertThat(convo.getNotificationChannel()) + .isEqualTo(expected.get(convo.getNotificationChannel().getId())); + assertThat(convo.getParentChannelLabel()) + .isEqualTo(expectedParentLabel.get(convo.getNotificationChannel().getId())); + assertThat(convo.getGroupLabel()) + .isEqualTo(expectedGroup.get(convo.getNotificationChannel().getId())); + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index ebe4ab99663a..a0ea7290ec01 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -29,6 +29,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_TASK_CLOSE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -140,6 +141,19 @@ public class ActivityRecordTests extends ActivityTestsBase { } @Test + public void testRemoveChildWithOverlayActivity() { + final ActivityRecord overlayActivity = + new ActivityBuilder(mService).setTask(mTask).build(); + overlayActivity.setTaskOverlay(true); + final ActivityRecord overlayActivity2 = + new ActivityBuilder(mService).setTask(mTask).build(); + overlayActivity2.setTaskOverlay(true); + + mTask.removeChild(overlayActivity2, "test"); + verify(mSupervisor, never()).removeTask(any(), anyBoolean(), anyBoolean(), any()); + } + + @Test public void testNoCleanupMovingActivityInSameStack() { final Task newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build(); mActivity.reparent(newTask, 0, null /*reason*/); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java new file mode 100644 index 000000000000..c1a1d5ecd3c8 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.server.wm.testing.Assert.assertThrows; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.res.Resources; +import android.platform.test.annotations.Presubmit; + +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Test; + +@Presubmit +public class DisplayAreaProviderTest { + + @Test + public void testFromResources_emptyProvider() { + Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider("")), + Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class)); + } + + @Test + public void testFromResources_nullProvider() { + Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(null)), + Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class)); + } + + @Test + public void testFromResources_customProvider() { + Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider( + TestProvider.class.getName())), Matchers.instanceOf(TestProvider.class)); + } + + @Test + public void testFromResources_badProvider_notImplementingProviderInterface() { + assertThrows(IllegalStateException.class, () -> { + DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider( + Object.class.getName())); + }); + } + + @Test + public void testFromResources_badProvider_doesntExist() { + assertThrows(IllegalStateException.class, () -> { + DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider( + "com.android.wmtests.nonexistent.Provider")); + }); + } + + private static Resources resourcesWithProvider(String provider) { + Resources mock = mock(Resources.class); + when(mock.getString( + com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider)) + .thenReturn(provider); + return mock; + } + + static class TestProvider implements DisplayAreaPolicy.Provider { + + @Override + public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content, + DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, + DisplayContent.TaskContainers taskContainers) { + throw new RuntimeException("test stub"); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 9dbaa4c9ca82..e6291a4c34ac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -95,8 +95,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Before public void setUp() throws Exception { - updateDisplayFrames(); - mWindow = spy(createWindow(null, TYPE_APPLICATION, "window")); // We only test window frames set by DisplayPolicy, so here prevents computeFrameLw from // changing those frames. @@ -106,6 +104,8 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { attrs.width = MATCH_PARENT; attrs.height = MATCH_PARENT; attrs.format = PixelFormat.TRANSLUCENT; + + updateDisplayFrames(); } @After @@ -126,6 +126,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { private void updateDisplayFrames() { mFrames = createDisplayFrames(); + mDisplayContent.mDisplayFrames = mFrames; } private DisplayFrames createDisplayFrames() { @@ -180,63 +181,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void layoutWindowLw_appDrawsBars() { - assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); - - mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mWindow.mAttrs.setFitInsetsTypes(Type.systemBars()); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0); - assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - } - - @Test - public void layoutWindowLw_forceAppDrawBars() { - assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); - - mWindow.mAttrs.privateFlags = PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; - mWindow.mAttrs.setFitInsetsTypes(Type.systemBars()); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), 0, 0); - assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0); - assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - } - - @Test - public void layoutWindowLw_onlyDrawBottomBar() { - assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); - - mWindow.mAttrs.flags = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; - mWindow.mAttrs.setFitInsetsTypes(Type.systemBars()); - addWindow(mWindow); - - mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); - mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); - - assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDisplayFrameLw(), STATUS_BAR_HEIGHT, 0); - assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, 0); - assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); - assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); - } - - @Test public void layoutWindowLw_fitAllSides() { assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); @@ -273,7 +217,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void layoutWindowLw_fitMax() { + public void layoutWindowLw_fitInsetsIgnoringVisibility() { assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); final InsetsState state = @@ -295,7 +239,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { } @Test - public void layoutWindowLw_fitNonMax() { + public void layoutWindowLw_fitInsetsNotIgnoringVisibility() { assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); final InsetsState state = @@ -398,6 +342,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); addWindow(mWindow); mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -416,6 +361,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; addWindow(mWindow); @@ -435,6 +381,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; mWindow.mAttrs.setFitInsetsTypes( mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars()); @@ -456,6 +403,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow) .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); @@ -477,6 +425,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow) .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); @@ -501,6 +450,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); addWindow(mWindow); mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -521,6 +471,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); addWindow(mWindow); mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -541,6 +492,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; mWindow.mAttrs.setFitInsetsTypes( mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars()); @@ -581,6 +533,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; mWindow.mAttrs.setFitInsetsTypes( mWindow.mAttrs.getFitInsetsTypes() & ~Type.statusBars()); @@ -603,6 +556,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE; addWindow(mWindow); @@ -625,6 +579,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { public void layoutWindowLw_withForwardInset_SoftInputAdjustNothing() { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING; addWindow(mWindow); @@ -849,7 +804,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { final String prefix = ""; final InsetsState simulatedInsetsState = new InsetsState(); final DisplayFrames simulatedDisplayFrames = createDisplayFrames(); - mDisplayContent.mDisplayFrames = mFrames; mDisplayPolicy.beginLayoutLw(mFrames, uiMode); mDisplayContent.getInsetsStateController().onPostLayout(); mDisplayPolicy.simulateLayoutDisplay(simulatedDisplayFrames, simulatedInsetsState, uiMode); @@ -867,6 +821,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { // Exclude comparing IME insets because currently the simulated layout only focuses on the // insets from status bar and navigation bar. realInsetsState.removeSource(InsetsState.ITYPE_IME); + realInsetsState.removeSource(InsetsState.ITYPE_CAPTION_BAR); realInsetsState.dump(prefix, new PrintWriter(realInsetsDump)); final StringWriter simulatedInsetsDump = new StringWriter(); simulatedInsetsState.dump(prefix, new PrintWriter(simulatedInsetsDump)); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index e71225579989..eae007d3a767 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -29,12 +29,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; import android.platform.test.annotations.Presubmit; import android.util.IntArray; @@ -122,13 +123,13 @@ public class InsetsPolicyTest extends WindowTestsBase { // TODO: adjust this test if we pretend to the app that it's still able to control it. @Test public void testControlsForDispatch_forceStatusBarVisible() { - addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |= + addWindow(TYPE_STATUS_BAR, "statusBar").mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; addWindow(TYPE_NAVIGATION_BAR, "navBar"); final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); - // The app must not control the top bar. + // The app must not control the status bar. assertNotNull(controls); assertEquals(1, controls.length); } @@ -137,6 +138,7 @@ public class InsetsPolicyTest extends WindowTestsBase { public void testControlsForDispatch_statusBarForceShowNavigation() { addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |= PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; + addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); @@ -169,7 +171,8 @@ public class InsetsPolicyTest extends WindowTestsBase { .getControllableInsetProvider().getSource().setVisible(false); final WindowState app = addWindow(TYPE_APPLICATION, "app"); - final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); + doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient( IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR})); @@ -184,7 +187,7 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testShowTransientBars_topCanBeTransient_appGetsTopFakeControl() { + public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() { // Adding app window before setting source visibility is to prevent the visibility from // being cleared by InsetsSourceProvider.updateVisibility. final WindowState app = addWindow(TYPE_APPLICATION, "app"); @@ -194,14 +197,15 @@ public class InsetsPolicyTest extends WindowTestsBase { addWindow(TYPE_NAVIGATION_BAR, "navBar") .getControllableInsetProvider().getSource().setVisible(true); - final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); + doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient( IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR})); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(app); - // The app must get the fake control of the top bar, and must get the real control of the + // The app must get the fake control of the status bar, and must get the real control of the // navigation bar. assertEquals(2, controls.length); for (int i = controls.length - 1; i >= 0; i--) { @@ -222,7 +226,8 @@ public class InsetsPolicyTest extends WindowTestsBase { .getControllableInsetProvider().getSource().setVisible(false); final WindowState app = addWindow(TYPE_APPLICATION, "app"); - final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); + doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient( IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR})); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 39cdd2cb907e..5cf1fbbacaf4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -20,7 +20,9 @@ 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.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; @@ -33,14 +35,14 @@ import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.test.InsetsModeSession; +import androidx.test.filters.FlakyTest; +import androidx.test.filters.SmallTest; + import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; -import androidx.test.filters.FlakyTest; -import androidx.test.filters.SmallTest; - @SmallTest @FlakyTest(detail = "Promote to pre-submit once confirmed stable.") @Presubmit @@ -88,6 +90,10 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); + + // IME cannot be the IME target. + ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); @@ -98,6 +104,10 @@ public class InsetsStateControllerTest extends WindowTestsBase { public void testImeForDispatch() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); + + // IME cannot be the IME target. + ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; + InsetsSourceProvider statusBarProvider = getController().getSourceProvider(ITYPE_STATUS_BAR); statusBarProvider.setWindow(statusBar, null, ((displayFrames, windowState, rect) -> 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 6e738e90ad9c..0312df6d34fc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -393,11 +393,19 @@ public class TaskOrganizerTests extends WindowTestsBase { RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks( + mDisplayContent.mDisplayId, null /* activityTypes */).size(); + final ActivityStack stack = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); final ActivityStack stack2 = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); + // Check getRootTasks works + List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks( + mDisplayContent.mDisplayId, null /* activityTypes */); + assertEquals(initialRootTaskCount + 2, roots.size()); + lastReportedTiles.clear(); WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */); @@ -424,11 +432,18 @@ public class TaskOrganizerTests extends WindowTestsBase { // Check the getChildren call List<RunningTaskInfo> children = - mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token); + mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token, + null /* activityTypes */); assertEquals(2, children.size()); - children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token); + children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token, + null /* activityTypes */); assertEquals(0, children.size()); + // Check that getRootTasks doesn't include children of tiles + roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(mDisplayContent.mDisplayId, + null /* activityTypes */); + assertEquals(initialRootTaskCount, roots.size()); + lastReportedTiles.clear(); wct = new WindowContainerTransaction(); wct.reorder(stack2.mRemoteToken, true /* onTop */); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java index 7aaf3fbccb5e..52b465ff4634 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java @@ -20,7 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.server.wm.TaskPositioner.MIN_ASPECT; +import static com.android.internal.policy.TaskResizingAlgorithm.MIN_ASPECT; import static com.android.server.wm.WindowManagerService.dipToPixel; import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP; import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP; diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 553bcff931d2..e97cfaf0afa6 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -123,6 +123,14 @@ public final class CarrierAppUtils { return userContext.getContentResolver(); } + private static boolean isUpdatedSystemApp(ApplicationInfo ai) { + if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + return true; + } + + return false; + } + /** * Disable carrier apps until they are privileged * Must be public b/c framework unit tests can't access package-private methods. @@ -137,7 +145,7 @@ public final class CarrierAppUtils { PackageManager packageManager = context.getPackageManager(); PermissionManager permissionManager = (PermissionManager) context.getSystemService(Context.PERMISSION_SERVICE); - List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper( + List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper( userId, systemCarrierAppsDisabledUntilUsed, context); if (candidates == null || candidates.isEmpty()) { return; @@ -176,7 +184,7 @@ public final class CarrierAppUtils { if (hasPrivileges) { // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (enabledSetting + if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED @@ -230,7 +238,7 @@ public final class CarrierAppUtils { } else { // No carrier privileges // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (enabledSetting + if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { Log.i(TAG, "Update state(" + packageName @@ -361,29 +369,6 @@ public final class CarrierAppUtils { return apps; } - private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper( - int userId, ArraySet<String> systemCarrierAppsDisabledUntilUsed, Context context) { - if (systemCarrierAppsDisabledUntilUsed == null) { - return null; - } - - int size = systemCarrierAppsDisabledUntilUsed.size(); - if (size == 0) { - return null; - } - - List<ApplicationInfo> apps = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); - ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp(userId, packageName, context); - if (ai != null) { - apps.add(ai); - } - } - return apps; - } - private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper( int userId, ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed, Context context) { @@ -395,11 +380,11 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp( + getApplicationInfoIfSystemApp( userId, associatedAppPackages.get(j), context); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. - if (ai != null) { + if (ai != null && !isUpdatedSystemApp(ai)) { List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage); if (appList == null) { appList = new ArrayList<>(); @@ -413,26 +398,6 @@ public final class CarrierAppUtils { } @Nullable - private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp( - int userId, String packageName, Context context) { - try { - ApplicationInfo ai = context.createContextAsUser(UserHandle.of(userId), 0) - .getPackageManager() - .getApplicationInfo(packageName, - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS - | PackageManager.MATCH_SYSTEM_ONLY - | PackageManager.MATCH_FACTORY_ONLY); - if (ai != null) { - return ai; - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Could not reach PackageManager", e); - } - return null; - } - - @Nullable private static ApplicationInfo getApplicationInfoIfSystemApp( int userId, String packageName, Context context) { try { diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java index e16fffa69f8a..e1aec0a593b4 100644 --- a/telephony/java/android/telephony/euicc/EuiccCardManager.java +++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.os.Binder; import android.os.RemoteException; import android.service.euicc.EuiccProfileInfo; import android.telephony.TelephonyFrameworkInitializer; @@ -168,7 +169,12 @@ public class EuiccCardManager { new IGetAllProfilesCallback.Stub() { @Override public void onComplete(int resultCode, EuiccProfileInfo[] profiles) { - executor.execute(() -> callback.onComplete(resultCode, profiles)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, profiles)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -192,7 +198,12 @@ public class EuiccCardManager { new IGetProfileCallback.Stub() { @Override public void onComplete(int resultCode, EuiccProfileInfo profile) { - executor.execute(() -> callback.onComplete(resultCode, profile)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, profile)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -217,7 +228,12 @@ public class EuiccCardManager { refresh, new IDisableProfileCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -243,7 +259,12 @@ public class EuiccCardManager { refresh, new ISwitchToProfileCallback.Stub() { @Override public void onComplete(int resultCode, EuiccProfileInfo profile) { - executor.execute(() -> callback.onComplete(resultCode, profile)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, profile)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -268,7 +289,12 @@ public class EuiccCardManager { nickname, new ISetNicknameCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -292,7 +318,12 @@ public class EuiccCardManager { new IDeleteProfileCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -317,7 +348,12 @@ public class EuiccCardManager { new IResetMemoryCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -340,7 +376,12 @@ public class EuiccCardManager { new IGetDefaultSmdpAddressCallback.Stub() { @Override public void onComplete(int resultCode, String address) { - executor.execute(() -> callback.onComplete(resultCode, address)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, address)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -363,7 +404,12 @@ public class EuiccCardManager { new IGetSmdsAddressCallback.Stub() { @Override public void onComplete(int resultCode, String address) { - executor.execute(() -> callback.onComplete(resultCode, address)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, address)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -388,7 +434,12 @@ public class EuiccCardManager { new ISetDefaultSmdpAddressCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -411,7 +462,12 @@ public class EuiccCardManager { new IGetRulesAuthTableCallback.Stub() { @Override public void onComplete(int resultCode, EuiccRulesAuthTable rat) { - executor.execute(() -> callback.onComplete(resultCode, rat)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, rat)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -434,7 +490,12 @@ public class EuiccCardManager { new IGetEuiccChallengeCallback.Stub() { @Override public void onComplete(int resultCode, byte[] challenge) { - executor.execute(() -> callback.onComplete(resultCode, challenge)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, challenge)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -457,7 +518,12 @@ public class EuiccCardManager { new IGetEuiccInfo1Callback.Stub() { @Override public void onComplete(int resultCode, byte[] info) { - executor.execute(() -> callback.onComplete(resultCode, info)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, info)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -480,7 +546,12 @@ public class EuiccCardManager { new IGetEuiccInfo2Callback.Stub() { @Override public void onComplete(int resultCode, byte[] info) { - executor.execute(() -> callback.onComplete(resultCode, info)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, info)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -522,7 +593,12 @@ public class EuiccCardManager { new IAuthenticateServerCallback.Stub() { @Override public void onComplete(int resultCode, byte[] response) { - executor.execute(() -> callback.onComplete(resultCode, response)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, response)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -561,7 +637,12 @@ public class EuiccCardManager { new IPrepareDownloadCallback.Stub() { @Override public void onComplete(int resultCode, byte[] response) { - executor.execute(() -> callback.onComplete(resultCode, response)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, response)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -589,7 +670,12 @@ public class EuiccCardManager { new ILoadBoundProfilePackageCallback.Stub() { @Override public void onComplete(int resultCode, byte[] response) { - executor.execute(() -> callback.onComplete(resultCode, response)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, response)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -619,7 +705,12 @@ public class EuiccCardManager { new ICancelSessionCallback.Stub() { @Override public void onComplete(int resultCode, byte[] response) { - executor.execute(() -> callback.onComplete(resultCode, response)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, response)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -643,7 +734,13 @@ public class EuiccCardManager { new IListNotificationsCallback.Stub() { @Override public void onComplete(int resultCode, EuiccNotification[] notifications) { - executor.execute(() -> callback.onComplete(resultCode, notifications)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete( + resultCode, notifications)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -667,7 +764,13 @@ public class EuiccCardManager { events, new IRetrieveNotificationListCallback.Stub() { @Override public void onComplete(int resultCode, EuiccNotification[] notifications) { - executor.execute(() -> callback.onComplete(resultCode, notifications)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete( + resultCode, notifications)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -691,7 +794,13 @@ public class EuiccCardManager { seqNumber, new IRetrieveNotificationCallback.Stub() { @Override public void onComplete(int resultCode, EuiccNotification notification) { - executor.execute(() -> callback.onComplete(resultCode, notification)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete( + resultCode, notification)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { @@ -718,7 +827,12 @@ public class EuiccCardManager { new IRemoveNotificationFromListCallback.Stub() { @Override public void onComplete(int resultCode) { - executor.execute(() -> callback.onComplete(resultCode, null)); + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } } }); } catch (RemoteException e) { diff --git a/tests/PlatformCompatGating/Android.bp b/tests/PlatformCompatGating/Android.bp index 74dfde848191..342c47de755a 100644 --- a/tests/PlatformCompatGating/Android.bp +++ b/tests/PlatformCompatGating/Android.bp @@ -18,6 +18,7 @@ android_test { name: "PlatformCompatGating", // Only compile source java files in this apk. srcs: ["src/**/*.java"], + test_suites: ["device-tests"], static_libs: [ "junit", "androidx.test.runner", diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 98e7b4e1b430..89005da27709 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -36,6 +36,15 @@ java_test_host { } java_test_host { + name: "NetworkStagedRollbackTest", + srcs: ["NetworkStagedRollbackTest/src/**/*.java"], + libs: ["tradefed"], + static_libs: ["testng"], + test_suites: ["general-tests"], + test_config: "NetworkStagedRollbackTest.xml", +} + +java_test_host { name: "MultiUserRollbackTest", srcs: ["MultiUserRollbackTest/src/**/*.java"], libs: ["tradefed"], diff --git a/tests/RollbackTest/NetworkStagedRollbackTest.xml b/tests/RollbackTest/NetworkStagedRollbackTest.xml new file mode 100644 index 000000000000..a465a4fe578b --- /dev/null +++ b/tests/RollbackTest/NetworkStagedRollbackTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs the network staged rollback tests"> + <option name="test-suite-tag" value="NetworkStagedRollbackTest" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="RollbackTest.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" value="com.android.tests.rollback.host.NetworkStagedRollbackTest" /> + </test> +</configuration> diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java new file mode 100644 index 000000000000..2c2e8282ff51 --- /dev/null +++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.rollback.host; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Runs the network rollback tests. + */ +@RunWith(DeviceJUnit4ClassRunner.class) +public class NetworkStagedRollbackTest extends BaseHostJUnit4Test { + /** + * Tests failed network health check triggers watchdog staged rollbacks. + */ + @Test + public void testNetworkFailedRollback() throws Exception { + } + + /** + * Tests passed network health check does not trigger watchdog staged rollbacks. + */ + @Test + public void testNetworkPassedDoesNotRollback() throws Exception { + } +} diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml index a14b01c57b1b..f2c0f86c3ce1 100644 --- a/tests/RollbackTest/RollbackTest.xml +++ b/tests/RollbackTest/RollbackTest.xml @@ -22,9 +22,9 @@ <option name="package" value="com.android.tests.rollback" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> - <!-- Exclude the StagedRollbackTest and MultiUserRollbackTest tests, which need to be - specially driven from the StagedRollbackTest and MultiUserRollbackTest host test --> + <!-- Exclude the device tests which need to be specially driven from the host tests --> <option name="exclude-filter" value="com.android.tests.rollback.StagedRollbackTest" /> + <option name="exclude-filter" value="com.android.tests.rollback.NetworkStagedRollbackTest" /> <option name="exclude-filter" value="com.android.tests.rollback.MultiUserRollbackTest" /> </test> </configuration> diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java new file mode 100644 index 000000000000..04004d6abb5a --- /dev/null +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tests.rollback; + +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class NetworkStagedRollbackTest { +} diff --git a/tests/net/Android.bp b/tests/net/Android.bp index b2f384ac8360..124b6609f687 100644 --- a/tests/net/Android.bp +++ b/tests/net/Android.bp @@ -8,7 +8,6 @@ java_defaults { "libbacktrace", "libbase", "libbinder", - "libbinderthreadstate", "libbpf", "libbpf_android", "libc++", diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 2af118c40138..5aa32f868104 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -391,6 +391,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["uses-split"].Action(RequiredNameIsJavaPackage); manifest_action["queries"]["package"].Action(RequiredNameIsJavaPackage); manifest_action["queries"]["intent"] = intent_filter_action; + manifest_action["queries"]["provider"].Action(RequiredAndroidAttribute("authorities")); // TODO: more complicated component name tag manifest_action["key-sets"]["key-set"]["public-key"]; diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 221f64440969..d4e024dbe0c7 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -270,4 +270,6 @@ interface IWifiManager void setScanThrottleEnabled(boolean enable); boolean isScanThrottleEnabled(); + + Map getAllMatchingPasspointProfilesForScanResults(in List<ScanResult> scanResult); } diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index c8fd2434e64f..0db3313ae137 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -519,6 +519,9 @@ public final class SoftApConfiguration implements Parcelable { case BAND_5GHZ: wifiConfig.apBand = WifiConfiguration.AP_BAND_5GHZ; break; + case BAND_2GHZ | BAND_5GHZ: + wifiConfig.apBand = WifiConfiguration.AP_BAND_ANY; + break; case BAND_ANY: wifiConfig.apBand = WifiConfiguration.AP_BAND_ANY; break; diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 7c3d0b92dd0a..a720236689d3 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -2112,7 +2112,8 @@ public class WifiConfiguration implements Parcelable { return !TextUtils.isEmpty(FQDN) && !TextUtils.isEmpty(providerFriendlyName) && enterpriseConfig != null - && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE; + && enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE + && !TextUtils.isEmpty(mPasspointUniqueId); } /** @@ -2494,12 +2495,17 @@ public class WifiConfiguration implements Parcelable { */ @NonNull public String getKey() { - String key = providerFriendlyName == null - ? getSsidAndSecurityTypeString() - : FQDN + KeyMgmt.strings[KeyMgmt.WPA_EAP]; + // Passpoint ephemeral networks have their unique identifier set. Return it as is to be + // able to match internally. + if (mPasspointUniqueId != null) { + return mPasspointUniqueId; + } + + String key = getSsidAndSecurityTypeString(); if (!shared) { key += "-" + UserHandle.getUserHandleForUid(creatorUid).getIdentifier(); } + return key; } @@ -2754,6 +2760,7 @@ public class WifiConfiguration implements Parcelable { requirePMF = source.requirePMF; updateIdentifier = source.updateIdentifier; carrierId = source.carrierId; + mPasspointUniqueId = source.mPasspointUniqueId; } } @@ -2826,6 +2833,7 @@ public class WifiConfiguration implements Parcelable { dest.writeInt(osu ? 1 : 0); dest.writeLong(randomizedMacExpirationTimeMs); dest.writeInt(carrierId); + dest.writeString(mPasspointUniqueId); } /** Implement the Parcelable interface {@hide} */ @@ -2900,6 +2908,7 @@ public class WifiConfiguration implements Parcelable { config.osu = in.readInt() != 0; config.randomizedMacExpirationTimeMs = in.readLong(); config.carrierId = in.readInt(); + config.mPasspointUniqueId = in.readString(); return config; } @@ -2907,4 +2916,28 @@ public class WifiConfiguration implements Parcelable { return new WifiConfiguration[size]; } }; + + /** + * Passpoint Unique identifier + * @hide + */ + private String mPasspointUniqueId = null; + + /** + * Set the Passpoint unique identifier + * @param uniqueId Passpoint unique identifier to be set + * @hide + */ + public void setPasspointUniqueId(String uniqueId) { + mPasspointUniqueId = uniqueId; + } + + /** + * Set the Passpoint unique identifier + * @hide + */ + public String getPasspointUniqueId() { + return mPasspointUniqueId; + } + } diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java index 24b2a8e8994f..0c306b4fb8cc 100644 --- a/wifi/java/android/net/wifi/WifiInfo.java +++ b/wifi/java/android/net/wifi/WifiInfo.java @@ -291,6 +291,11 @@ public class WifiInfo implements Parcelable { */ private boolean mMeteredHint; + /** + * Passpoint unique key + */ + private String mPasspointUniqueId; + /** @hide */ @UnsupportedAppUsage public WifiInfo() { @@ -322,6 +327,7 @@ public class WifiInfo implements Parcelable { setRequestingPackageName(null); setFQDN(null); setProviderFriendlyName(null); + setPasspointUniqueId(null); txBad = 0; txSuccess = 0; rxSuccess = 0; @@ -370,6 +376,7 @@ public class WifiInfo implements Parcelable { mWifiStandard = source.mWifiStandard; mMaxSupportedTxLinkSpeed = source.mMaxSupportedTxLinkSpeed; mMaxSupportedRxLinkSpeed = source.mMaxSupportedRxLinkSpeed; + mPasspointUniqueId = source.mPasspointUniqueId; } } @@ -977,6 +984,7 @@ public class WifiInfo implements Parcelable { dest.writeInt(mWifiStandard); dest.writeInt(mMaxSupportedTxLinkSpeed); dest.writeInt(mMaxSupportedRxLinkSpeed); + dest.writeString(mPasspointUniqueId); } /** Implement the Parcelable interface {@hide} */ @@ -1021,6 +1029,7 @@ public class WifiInfo implements Parcelable { info.mWifiStandard = in.readInt(); info.mMaxSupportedTxLinkSpeed = in.readInt(); info.mMaxSupportedRxLinkSpeed = in.readInt(); + info.mPasspointUniqueId = in.readString(); return info; } @@ -1028,4 +1037,24 @@ public class WifiInfo implements Parcelable { return new WifiInfo[size]; } }; + + /** + * Set the Passpoint unique identifier for the current connection + * + * @param passpointUniqueId Unique identifier + * @hide + */ + public void setPasspointUniqueId(@Nullable String passpointUniqueId) { + mPasspointUniqueId = passpointUniqueId; + } + + /** + * Get the Passpoint unique identifier for the current connection + * + * @return Passpoint unique identifier + * @hide + */ + public @Nullable String getPasspointUniqueId() { + return mPasspointUniqueId; + } } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 1682023c3312..fb30910c17b1 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -1400,8 +1400,7 @@ public class WifiManager { List<Pair<WifiConfiguration, Map<Integer, List<ScanResult>>>> configs = new ArrayList<>(); try { Map<String, Map<Integer, List<ScanResult>>> results = - mService.getAllMatchingFqdnsForScanResults( - scanResults); + mService.getAllMatchingPasspointProfilesForScanResults(scanResults); if (results.isEmpty()) { return configs; } @@ -1409,8 +1408,8 @@ public class WifiManager { mService.getWifiConfigsForPasspointProfiles( new ArrayList<>(results.keySet())); for (WifiConfiguration configuration : wifiConfigurations) { - Map<Integer, List<ScanResult>> scanResultsPerNetworkType = results.get( - configuration.FQDN); + Map<Integer, List<ScanResult>> scanResultsPerNetworkType = + results.get(configuration.getKey()); if (scanResultsPerNetworkType != null) { configs.add(Pair.create(configuration, scanResultsPerNetworkType)); } @@ -1962,9 +1961,11 @@ public class WifiManager { * for connecting to Passpoint networks that are operated by the Passpoint * service provider specified in the configuration. * - * Each configuration is uniquely identified by its FQDN (Fully Qualified Domain - * Name). In the case when there is an existing configuration with the same - * FQDN, the new configuration will replace the existing configuration. + * Each configuration is uniquely identified by a unique key which depends on the contents of + * the configuration. This allows the caller to install multiple profiles with the same FQDN + * (Fully qualified domain name). Therefore, in order to update an existing profile, it is + * first required to remove it using {@link WifiManager#removePasspointConfiguration(String)}. + * Otherwise, a new profile will be added with both configuration. * * @param config The Passpoint configuration to be added * @throws IllegalArgumentException if configuration is invalid or Passpoint is not enabled on @@ -2982,7 +2983,7 @@ public class WifiManager { } /** - * Start Soft AP (hotspot) mode with the specified configuration. + * Start Soft AP (hotspot) mode for tethering purposes with the specified configuration. * Note that starting Soft AP mode may disable station mode operation if the device does not * support concurrency. * @param wifiConfig SSID, security and channel details as part of WifiConfiguration, or null to @@ -3278,7 +3279,7 @@ public class WifiManager { } /** - * Gets the Wi-Fi enabled state. + * Gets the tethered Wi-Fi hotspot enabled state. * @return One of {@link #WIFI_AP_STATE_DISABLED}, * {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED}, * {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED} @@ -3297,8 +3298,8 @@ public class WifiManager { } /** - * Return whether Wi-Fi AP is enabled or disabled. - * @return {@code true} if Wi-Fi AP is enabled + * Return whether tethered Wi-Fi AP is enabled or disabled. + * @return {@code true} if tethered Wi-Fi AP is enabled * @see #getWifiApState() * * @hide @@ -3310,7 +3311,7 @@ public class WifiManager { } /** - * Gets the Wi-Fi AP Configuration. + * Gets the tethered Wi-Fi AP Configuration. * @return AP details in WifiConfiguration * * Note that AP detail may contain configuration which is cannot be represented @@ -3332,7 +3333,7 @@ public class WifiManager { } /** - * Gets the Wi-Fi AP Configuration. + * Gets the Wi-Fi tethered AP Configuration. * @return AP details in {@link SoftApConfiguration} * * @hide @@ -3349,7 +3350,7 @@ public class WifiManager { } /** - * Sets the Wi-Fi AP Configuration. + * Sets the tethered Wi-Fi AP Configuration. * @return {@code true} if the operation succeeded, {@code false} otherwise * * @deprecated This API is deprecated. Use {@link #setSoftApConfiguration(SoftApConfiguration)} @@ -3368,9 +3369,9 @@ public class WifiManager { } /** - * Sets the Wi-Fi AP Configuration. + * Sets the tethered Wi-Fi AP Configuration. * - * If the API is called while the soft AP is enabled, the configuration will apply to + * If the API is called while the tethered soft AP is enabled, the configuration will apply to * the current soft AP if the new configuration only includes * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)} diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java index 720149695abc..a854a4ba3ae5 100644 --- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java +++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java @@ -569,6 +569,7 @@ public final class WifiNetworkSuggestion implements Parcelable { private WifiConfiguration buildWifiConfigurationForPasspoint() { WifiConfiguration wifiConfiguration = new WifiConfiguration(); wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn(); + wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId()); wifiConfiguration.priority = mPriority; wifiConfiguration.meteredOverride = mIsMetered ? WifiConfiguration.METERED_OVERRIDE_METERED @@ -804,7 +805,7 @@ public final class WifiNetworkSuggestion implements Parcelable { @Override public int hashCode() { return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID, - wifiConfiguration.allowedKeyManagement, wifiConfiguration.FQDN); + wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey()); } /** @@ -827,7 +828,8 @@ public final class WifiNetworkSuggestion implements Parcelable { && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID) && Objects.equals(this.wifiConfiguration.allowedKeyManagement, lhs.wifiConfiguration.allowedKeyManagement) - && TextUtils.equals(this.wifiConfiguration.FQDN, lhs.wifiConfiguration.FQDN); + && TextUtils.equals(this.wifiConfiguration.getKey(), + lhs.wifiConfiguration.getKey()); } @Override diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java index 615331f65258..9f581849efca 100644 --- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java +++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java @@ -908,6 +908,9 @@ public final class PasspointConfiguration implements Parcelable { throw new IllegalStateException("Credential or HomeSP are not initialized"); } - return mHomeSp.getFqdn(); + StringBuilder sb = new StringBuilder(); + sb.append(String.format("%s_%x%x", mHomeSp.getFqdn(), mHomeSp.hashCode(), + mCredential.hashCode())); + return sb.toString(); } } diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java index 49a76c33d209..a5de3318f454 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java @@ -16,8 +16,8 @@ package android.net.wifi.hotspot2.pps; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; @@ -299,8 +299,10 @@ public final class HomeSp implements Parcelable { @Override public int hashCode() { - return Objects.hash(mFqdn, mFriendlyName, mIconUrl, mHomeNetworkIds, mMatchAllOis, - mMatchAnyOis, mOtherHomePartners, mRoamingConsortiumOis); + return Objects.hash(mFqdn, mFriendlyName, mIconUrl, + mHomeNetworkIds, Arrays.hashCode(mMatchAllOis), + Arrays.hashCode(mMatchAnyOis), Arrays.hashCode(mOtherHomePartners), + Arrays.hashCode(mRoamingConsortiumOis)); } @Override diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java index 2efdd97543a9..d9584885a045 100644 --- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java @@ -287,17 +287,43 @@ public class SoftApConfigurationTest { @Test public void testToWifiConfigurationWithSupportedParameter() { - SoftApConfiguration softApConfig = new SoftApConfiguration.Builder() + SoftApConfiguration softApConfig_2g = new SoftApConfiguration.Builder() + .setPassphrase("secretsecret", + SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) + .setChannel(11, SoftApConfiguration.BAND_2GHZ) + .setHiddenSsid(true) + .build(); + WifiConfiguration wifiConfig_2g = softApConfig_2g.toWifiConfiguration(); + assertThat(wifiConfig_2g.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK); + assertThat(wifiConfig_2g.preSharedKey).isEqualTo("secretsecret"); + assertThat(wifiConfig_2g.apBand).isEqualTo(WifiConfiguration.AP_BAND_2GHZ); + assertThat(wifiConfig_2g.apChannel).isEqualTo(11); + assertThat(wifiConfig_2g.hiddenSSID).isEqualTo(true); + + SoftApConfiguration softApConfig_5g = new SoftApConfiguration.Builder() .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) .setChannel(149, SoftApConfiguration.BAND_5GHZ) .setHiddenSsid(true) .build(); - WifiConfiguration wifiConfig = softApConfig.toWifiConfiguration(); - assertThat(wifiConfig.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK); - assertThat(wifiConfig.preSharedKey).isEqualTo("secretsecret"); - assertThat(wifiConfig.apBand).isEqualTo(WifiConfiguration.AP_BAND_5GHZ); - assertThat(wifiConfig.apChannel).isEqualTo(149); - assertThat(wifiConfig.hiddenSSID).isEqualTo(true); + WifiConfiguration wifiConfig_5g = softApConfig_5g.toWifiConfiguration(); + assertThat(wifiConfig_5g.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK); + assertThat(wifiConfig_5g.preSharedKey).isEqualTo("secretsecret"); + assertThat(wifiConfig_5g.apBand).isEqualTo(WifiConfiguration.AP_BAND_5GHZ); + assertThat(wifiConfig_5g.apChannel).isEqualTo(149); + assertThat(wifiConfig_5g.hiddenSSID).isEqualTo(true); + + SoftApConfiguration softApConfig_2g5g = new SoftApConfiguration.Builder() + .setPassphrase("secretsecret", + SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) + .setBand(SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + WifiConfiguration wifiConfig_2g5g = softApConfig_2g5g.toWifiConfiguration(); + assertThat(wifiConfig_2g5g.getAuthType()).isEqualTo(WifiConfiguration.KeyMgmt.WPA2_PSK); + assertThat(wifiConfig_2g5g.preSharedKey).isEqualTo("secretsecret"); + assertThat(wifiConfig_2g5g.apBand).isEqualTo(WifiConfiguration.AP_BAND_ANY); + assertThat(wifiConfig_2g5g.apChannel).isEqualTo(0); + assertThat(wifiConfig_2g5g.hiddenSSID).isEqualTo(true); } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 53e9755eaf52..847040ca914a 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -1551,14 +1551,15 @@ public class WifiManagerTest { */ @Test public void testGetAllMatchingWifiConfigs() throws Exception { - Map<String, List<ScanResult>> fqdns = new HashMap<>(); - fqdns.put("www.test.com", new ArrayList<>()); - when(mWifiService.getAllMatchingFqdnsForScanResults(any(List.class))).thenReturn(fqdns); + Map<String, List<ScanResult>> passpointProfiles = new HashMap<>(); + passpointProfiles.put("www.test.com_987a69bca26", new ArrayList<>()); + when(mWifiService.getAllMatchingPasspointProfilesForScanResults( + any(List.class))).thenReturn(passpointProfiles); InOrder inOrder = inOrder(mWifiService); mWifiManager.getAllMatchingWifiConfigs(new ArrayList<>()); - inOrder.verify(mWifiService).getAllMatchingFqdnsForScanResults(any(List.class)); + inOrder.verify(mWifiService).getAllMatchingPasspointProfilesForScanResults(any(List.class)); inOrder.verify(mWifiService).getWifiConfigsForPasspointProfiles(any(List.class)); } diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java index ce542c2bc4e9..8f6beb19091b 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java @@ -20,8 +20,11 @@ import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import android.net.wifi.hotspot2.pps.Credential; +import android.net.wifi.hotspot2.pps.HomeSp; import android.os.Parcel; import androidx.test.filters.SmallTest; @@ -367,16 +370,55 @@ public class PasspointConfigurationTest { } /** - * Verify that the unique identifier generated is correct. + * Verify that the unique identifier generated is identical for two instances * * @throws Exception */ @Test public void validateUniqueId() throws Exception { - PasspointConfiguration config = PasspointTestUtils.createConfig(); - String uniqueId; - uniqueId = config.getUniqueId(); - assertEquals(uniqueId, config.getHomeSp().getFqdn()); + PasspointConfiguration config1 = PasspointTestUtils.createConfig(); + PasspointConfiguration config2 = PasspointTestUtils.createConfig(); + + assertEquals(config1.getUniqueId(), config2.getUniqueId()); + } + + /** + * Verify that the unique identifier generated is different for two instances with different + * HomeSp node + * + * @throws Exception + */ + @Test + public void validateUniqueIdDifferentHomeSp() throws Exception { + PasspointConfiguration config1 = PasspointTestUtils.createConfig(); + + // Modify config2's RCOIs to a different set of values + PasspointConfiguration config2 = PasspointTestUtils.createConfig(); + HomeSp homeSp = config2.getHomeSp(); + homeSp.setRoamingConsortiumOis(new long[] {0xaa, 0xbb}); + config2.setHomeSp(homeSp); + + assertNotEquals(config1.getUniqueId(), config2.getUniqueId()); + } + + /** + * Verify that the unique identifier generated is different for two instances with different + * Credential node + * + * @throws Exception + */ + @Test + public void validateUniqueIdDifferentCredential() throws Exception { + PasspointConfiguration config1 = PasspointTestUtils.createConfig(); + + // Modify config2's RCOIs to a different set of values + PasspointConfiguration config2 = PasspointTestUtils.createConfig(); + Credential credential = config2.getCredential(); + credential.setRealm("realm2.example.com"); + credential.getSimCredential().setImsi("350460*"); + config2.setCredential(credential); + + assertNotEquals(config1.getUniqueId(), config2.getUniqueId()); } /** |